1 from django.utils.translation import ugettext as _
2 from utils import PickledObjectField
3 from threading import Thread
4 from forum.utils import html
8 class ActionQuerySet(CachedQuerySet):
9 def obj_from_datadict(self, datadict):
10 cls = ActionProxyMetaClass.types.get(datadict['action_type'], None)
13 obj.__dict__.update(datadict)
16 return super(ActionQuerySet, self).obj_from_datadict(datadict)
18 def get(self, *args, **kwargs):
19 return super(ActionQuerySet, self).get(*args, **kwargs).leaf()
21 class ActionManager(CachedManager):
22 use_for_related_fields = True
24 def get_query_set(self):
25 qs = ActionQuerySet(self.model)
27 if self.model is not Action:
28 return qs.filter(action_type=self.model.get_type())
32 def get_for_types(self, types, *args, **kwargs):
33 kwargs['action_type__in'] = [t.get_type() for t in types]
34 return self.get(*args, **kwargs)
37 class Action(BaseModel):
38 user = models.ForeignKey('User', related_name="actions")
39 ip = models.CharField(max_length=16)
40 node = models.ForeignKey('Node', null=True, related_name="actions")
41 action_type = models.CharField(max_length=16)
42 action_date = models.DateTimeField(default=datetime.datetime.now)
44 extra = PickledObjectField()
46 canceled = models.BooleanField(default=False)
47 canceled_by = models.ForeignKey('User', null=True, related_name="canceled_actions")
48 canceled_at = models.DateTimeField(null=True)
49 canceled_ip = models.CharField(max_length=16)
53 objects = ActionManager()
57 return self.action_date
63 def repute_users(self):
66 def process_data(self, **data):
69 def process_action(self):
72 def cancel_action(self):
79 def describe(self, viewer=None):
80 return self.__class__.__name__
82 def get_absolute_url(self):
84 return self.node.get_absolute_url()
86 return self.user.get_profile_url()
88 def repute(self, user, value):
89 repute = ActionRepute(action=self, user=user, value=value)
93 def cancel_reputes(self):
94 for repute in self.reputes.all():
95 cancel = ActionRepute(action=self, user=repute.user, value=(-repute.value), by_canceled=True)
99 leaf_cls = ActionProxyMetaClass.types.get(self.action_type, None)
106 leaf.__dict__.update(self._as_dict())
112 return re.sub(r'action$', '', cls.__name__.lower())
114 def save(self, data=None, *args, **kwargs):
118 self.action_type = self.__class__.get_type()
122 self.process_data(**data)
124 super(Action, self).save(*args, **kwargs)
127 if (self.node is None) or (not self.node.nis.wiki):
129 self.process_action()
130 self.trigger_hooks(True)
134 def delete(self, *args, **kwargs):
136 super(Action, self).delete(*args, **kwargs)
138 def cancel(self, user=None, ip=None):
139 if not self.canceled:
141 self.canceled_at = datetime.datetime.now()
142 self.canceled_by = (user is None) and self.user or user
144 self.canceled_ip = ip
146 self.cancel_reputes()
148 #self.trigger_hooks(False)
151 def get_current(cls, **kwargs):
152 kwargs['canceled'] = False
155 return cls.objects.get(**kwargs)
156 except cls.MultipleObjectsReturned:
157 logging.error("Got multiple values for action %s with args %s", cls.__name__,
158 ", ".join(["%s='%s'" % i for i in kwargs.items()]))
160 except cls.DoesNotExist:
165 if not Action.hooks.get(cls, None):
166 Action.hooks[cls] = []
168 Action.hooks[cls].append(fn)
170 def trigger_hooks(self, new=True):
171 thread = Thread(target=trigger_hooks_threaded, args=[self, Action.hooks, new])
172 thread.setDaemon(True)
178 def trigger_hooks_threaded(action, hooks, new):
179 for cls, hooklist in hooks.items():
180 if isinstance(action, cls):
181 for hook in hooklist:
183 hook(action=action, new=new)
186 logging.error("Error in %s hook: %s" % (cls.__name__, str(e)))
187 logging.error(traceback.format_exc())
189 class ActionProxyMetaClass(BaseMetaClass):
192 def __new__(cls, *args, **kwargs):
193 new_cls = super(ActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)
194 cls.types[new_cls.get_type()] = new_cls
202 class ActionProxy(Action):
203 __metaclass__ = ActionProxyMetaClass
205 def friendly_username(self, viewer, user):
206 return (viewer == user) and _('You') or user.username
208 def friendly_ownername(self, owner, user):
209 return (owner == user) and _('your') or user.username
211 def viewer_or_user_verb(self, viewer, user, viewer_verb, user_verb):
212 return (viewer == user) and viewer_verb or user_verb
214 def hyperlink(self, url, title, **attrs):
215 return html.hyperlink(url, title, **attrs)
217 def describe_node(self, viewer, node):
218 node_link = self.hyperlink(node.get_absolute_url(), node.headline)
221 node_desc = _("on %(link)s") % {'link': node_link}
223 node_desc = node_link
225 return _("%(user)s %(node_name)s %(node_desc)s") % {
226 'user': self.hyperlink(node.author.get_profile_url(), self.friendly_ownername(viewer, node.author)),
227 'node_name': node.friendly_name,
228 'node_desc': node_desc,
234 class DummyActionProxyMetaClass(type):
235 def __new__(cls, *args, **kwargs):
236 new_cls = super(DummyActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)
237 ActionProxyMetaClass.types[new_cls.get_type()] = new_cls
240 class DummyActionProxy(object):
241 __metaclass__ = DummyActionProxyMetaClass
245 def __init__(self, ip=None):
248 def process_data(self, **data):
251 def process_action(self):
254 def save(self, data=None):
255 self.process_action()
258 self.process_data(**data)
260 for hook in self.__class__.hooks:
265 return re.sub(r'action$', '', cls.__name__.lower())
273 class ActionRepute(models.Model):
274 action = models.ForeignKey(Action, related_name='reputes')
275 date = models.DateTimeField(default=datetime.datetime.now)
276 user = models.ForeignKey('User', related_name='reputes')
277 value = models.IntegerField(default=0)
278 by_canceled = models.BooleanField(default=False)
282 if self.value > 0: return self.value
287 if self.value < 0: return self.value
290 def save(self, *args, **kwargs):
291 super(ActionRepute, self).save(*args, **kwargs)
292 self.user.reputation += self.value
296 self.user.reputation -= self.value
298 super(ActionRepute, self).delete()