1 from django.utils.translation import ugettext as _
2 from django.utils.encoding import smart_unicode
4 from utils import PickledObjectField
5 from threading import Thread
6 from forum.utils import html
10 class ActionQuerySet(CachedQuerySet):
11 def obj_from_datadict(self, datadict):
12 cls = ActionProxyMetaClass.types.get(datadict['action_type'], None)
15 obj.__dict__.update(datadict)
18 return super(ActionQuerySet, self).obj_from_datadict(datadict)
20 def get(self, *args, **kwargs):
21 action = super(ActionQuerySet, self).get(*args, **kwargs).leaf
23 if not isinstance(action, self.model):
24 raise self.model.DoesNotExist()
28 class ActionManager(CachedManager):
29 use_for_related_fields = True
31 def get_query_set(self):
32 qs = ActionQuerySet(self.model)
34 if self.model is not Action:
35 return qs.filter(action_type=self.model.get_type())
39 def get_for_types(self, types, *args, **kwargs):
40 kwargs['action_type__in'] = [t.get_type() for t in types]
41 return self.get(*args, **kwargs)
44 class Action(BaseModel):
45 user = models.ForeignKey('User', related_name="actions")
46 real_user = models.ForeignKey('User', related_name="proxied_actions", null=True)
47 ip = models.CharField(max_length=39)
48 node = models.ForeignKey('Node', null=True, related_name="actions")
49 action_type = models.CharField(max_length=32)
50 action_date = models.DateTimeField(default=datetime.datetime.now)
52 extra = PickledObjectField()
54 canceled = models.BooleanField(default=False)
55 canceled_by = models.ForeignKey('User', null=True, related_name="canceled_actions")
56 canceled_at = models.DateTimeField(null=True)
57 canceled_ip = models.CharField(max_length=39)
61 objects = ActionManager()
65 return self.action_date
71 def repute_users(self):
74 def process_data(self, **data):
77 def process_action(self):
80 def cancel_action(self):
87 def describe(self, viewer=None):
88 return self.__class__.__name__
90 def get_absolute_url(self):
92 return self.node.get_absolute_url()
94 return self.user.get_profile_url()
96 def repute(self, user, value):
97 repute = ActionRepute(action=self, user=user, value=value)
101 def cancel_reputes(self):
102 for repute in self.reputes.all():
103 cancel = ActionRepute(action=self, user=repute.user, value=(-repute.value), by_canceled=True)
108 leaf_cls = ActionProxyMetaClass.types.get(self.action_type, None)
115 leaf.__dict__.update(self._as_dict())
121 return re.sub(r'action$', '', cls.__name__.lower())
123 def save(self, data=None, threaded=True, *args, **kwargs):
127 self.action_type = self.__class__.get_type()
131 self.process_data(**data)
133 super(Action, self).save(*args, **kwargs)
136 if (self.node is None) or (not self.node.nis.wiki):
140 self.process_action()
143 self.trigger_hooks(threaded, True)
147 def delete(self, *args, **kwargs):
149 super(Action, self).delete(*args, **kwargs)
151 def cancel(self, user=None, ip=None):
152 if not self.canceled:
154 self.canceled_at = datetime.datetime.now()
155 self.canceled_by = (user is None) and self.user or user
157 self.canceled_ip = ip
159 self.cancel_reputes()
161 #self.trigger_hooks(False)
164 def get_current(cls, **kwargs):
165 kwargs['canceled'] = False
168 return cls.objects.get(**kwargs)
169 except cls.MultipleObjectsReturned:
170 logging.error("Got multiple values for action %s with args %s", cls.__name__,
171 ", ".join(["%s='%s'" % i for i in kwargs.items()]))
173 except cls.DoesNotExist:
178 if not Action.hooks.get(cls, None):
179 Action.hooks[cls] = []
181 Action.hooks[cls].append(fn)
183 def trigger_hooks(self, threaded, new=True):
185 thread = Thread(target=trigger_hooks, args=[self, Action.hooks, new])
186 thread.setDaemon(True)
189 trigger_hooks(self, Action.hooks, new)
194 def trigger_hooks(action, hooks, new):
195 for cls, hooklist in hooks.items():
196 if isinstance(action, cls):
197 for hook in hooklist:
199 hook(action=action, new=new)
202 logging.error("Error in %s hook: %s" % (cls.__name__, str(e)))
203 logging.error(traceback.format_exc())
205 class ActionProxyMetaClass(BaseMetaClass):
208 def __new__(cls, *args, **kwargs):
209 new_cls = super(ActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)
210 cls.types[new_cls.get_type()] = new_cls
218 class ActionProxy(Action):
219 __metaclass__ = ActionProxyMetaClass
221 def friendly_username(self, viewer, user):
222 return (viewer == user) and _('You') or smart_unicode(user.username)
224 def friendly_ownername(self, owner, user):
225 return (owner == user) and _('your') or smart_unicode(user.username)
227 def viewer_or_user_verb(self, viewer, user, viewer_verb, user_verb):
228 return (viewer == user) and viewer_verb or user_verb
230 def hyperlink(self, url, title, **attrs):
231 return html.hyperlink(url, title, **attrs)
233 def describe_node(self, viewer, node):
234 node_link = self.hyperlink(node.get_absolute_url(), node.headline)
237 node_desc = _("on %(link)s") % {'link': node_link}
239 node_desc = node_link
241 return _("%(user)s %(node_name)s %(node_desc)s") % {
242 'user': self.hyperlink(node.author.get_profile_url(), self.friendly_ownername(viewer, node.author)),
243 'node_name': node.friendly_name,
244 'node_desc': node_desc,
247 def affected_links(self, viewer):
248 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()])])
253 class DummyActionProxyMetaClass(type):
254 def __new__(cls, *args, **kwargs):
255 new_cls = super(DummyActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)
256 ActionProxyMetaClass.types[new_cls.get_type()] = new_cls
259 class DummyActionProxy(object):
260 __metaclass__ = DummyActionProxyMetaClass
264 def __init__(self, ip=None):
267 def process_data(self, **data):
270 def process_action(self):
273 def save(self, data=None):
274 self.process_action()
277 self.process_data(**data)
279 for hook in self.__class__.hooks:
284 return re.sub(r'action$', '', cls.__name__.lower())
291 class ActionRepute(models.Model):
292 action = models.ForeignKey(Action, related_name='reputes')
293 date = models.DateTimeField(default=datetime.datetime.now)
294 user = models.ForeignKey('User', related_name='reputes')
295 value = models.IntegerField(default=0)
296 by_canceled = models.BooleanField(default=False)
300 if self.value > 0: return self.value
305 if self.value < 0: return self.value
308 def _add_to_rep(self, value):
309 if int(self.user.reputation + value) < 1 and not settings.ALLOW_NEGATIVE_REPUTATION:
312 return models.F('reputation') + value
314 def save(self, *args, **kwargs):
315 super(ActionRepute, self).save(*args, **kwargs)
316 self.user.reputation = self._add_to_rep(self.value)
320 self.user.reputation = self._add_to_rep(-self.value)
322 super(ActionRepute, self).delete()