-from django.utils.translation import ugettext as _\r
-from utils import PickledObjectField\r
-from threading import Thread\r
-from base import *\r
-import re\r
-\r
-class ActionManager(models.Manager):\r
- use_for_related_fields = True\r
-\r
- def get_query_set(self):\r
- qs = super(ActionManager, self).get_query_set().filter(canceled=False)\r
-\r
- if self.model is not Action:\r
- return qs.filter(action_type=self.model.get_type())\r
- else:\r
- return qs\r
-\r
- def get(self, *args, **kwargs):\r
- action = super(ActionManager, self).get(*args, **kwargs)\r
- if self.model == Action:\r
- return action.leaf()\r
- return action\r
-\r
- def get_for_types(self, types, *args, **kwargs):\r
- kwargs['action_type__in'] = [t.get_type() for t in types]\r
- return self.get(*args, **kwargs)\r
-\r
- \r
-\r
-class Action(models.Model):\r
- user = models.ForeignKey('User', related_name="actions")\r
- ip = models.CharField(max_length=16)\r
- node = models.ForeignKey('Node', null=True, related_name="actions")\r
- action_type = models.CharField(max_length=16)\r
- action_date = models.DateTimeField(default=datetime.datetime.now)\r
-\r
- extra = PickledObjectField()\r
-\r
- canceled = models.BooleanField(default=False)\r
- canceled_by = models.ForeignKey('User', null=True, related_name="canceled_actions")\r
- canceled_at = models.DateTimeField(null=True)\r
- canceled_ip = models.CharField(max_length=16)\r
-\r
- hooks = {}\r
-\r
- objects = ActionManager()\r
-\r
- @property\r
- def at(self):\r
- return self.action_date\r
-\r
- @property\r
- def by(self):\r
- return self.user\r
-\r
- def repute_users(self):\r
- pass\r
-\r
- def process_data(self, **data):\r
- pass\r
-\r
- def process_action(self):\r
- pass\r
-\r
- def cancel_action(self):\r
- pass\r
-\r
- @property\r
- def verb(self):\r
- return ""\r
-\r
- def describe(self, viewer=None):\r
- return ""\r
-\r
- def get_absolute_url(self):\r
- if self.node:\r
- return self.node.get_absolute_url()\r
- else:\r
- return self.user.get_profile_url()\r
-\r
- def repute(self, user, value):\r
- repute = ActionRepute(action=self, user=user, value=value)\r
- repute.save()\r
- return repute\r
-\r
- def cancel_reputes(self):\r
- for repute in self.reputes.all():\r
- cancel = ActionRepute(action=self, user=repute.user, value=(-repute.value), by_canceled=True)\r
- cancel.save()\r
-\r
- def leaf(self):\r
- leaf_cls = ActionProxyMetaClass.types.get(self.action_type, None)\r
-\r
- if leaf_cls is None:\r
- return self\r
-\r
- leaf = leaf_cls()\r
- leaf.__dict__ = self.__dict__\r
- return leaf\r
-\r
- @classmethod\r
- def get_type(cls):\r
- return re.sub(r'action$', '', cls.__name__.lower())\r
-\r
- def save(self, data=None, *args, **kwargs):\r
- isnew = False\r
-\r
- if not self.id:\r
- self.action_type = self.__class__.get_type()\r
- isnew = True\r
-\r
- if data:\r
- self.process_data(**data)\r
-\r
- super(Action, self).save(*args, **kwargs)\r
-\r
- if isnew:\r
- if (self.node is None) or (not self.node.wiki):\r
- self.repute_users()\r
- self.process_action()\r
- self.trigger_hooks(True)\r
-\r
- return self\r
-\r
- def delete(self):\r
- self.cancel_action()\r
- super(Action, self).delete()\r
-\r
- def cancel(self, user=None, ip=None):\r
- if not self.canceled:\r
- self.canceled = True\r
- self.canceled_at = datetime.datetime.now()\r
- self.canceled_by = (user is None) and self.user or user\r
- if ip:\r
- self.canceled_ip = ip\r
- self.save()\r
- self.cancel_reputes()\r
- self.cancel_action()\r
- #self.trigger_hooks(False)\r
-\r
- @classmethod\r
- def get_current(cls, **kwargs):\r
- kwargs['canceled'] = False\r
-\r
- try:\r
- return cls.objects.get(**kwargs)\r
- except cls.MultipleObjectsReturned:\r
- logging.error("Got multiple values for action %s with args %s", cls.__name__,\r
- ", ".join(["%s='%s'" % i for i in kwargs.items()]))\r
- raise\r
- except cls.DoesNotExist:\r
- return None\r
-\r
- @classmethod\r
- def hook(cls, fn):\r
- if not Action.hooks.get(cls, None):\r
- Action.hooks[cls] = []\r
-\r
- Action.hooks[cls].append(fn)\r
-\r
- def trigger_hooks(self, new=True):\r
- thread = Thread(target=trigger_hooks_threaded, args=[self, Action.hooks, new])\r
- thread.setDaemon(True)\r
- thread.start()\r
-\r
- class Meta:\r
- app_label = 'forum'\r
-\r
-def trigger_hooks_threaded(action, hooks, new):\r
- for cls, hooklist in hooks.items():\r
- if isinstance(action, cls):\r
- for hook in hooklist:\r
- try:\r
- hook(action=action, new=new)\r
- except Exception, e:\r
- logging.error("Error in %s hook: %s" % (cls.__name__, str(e)))\r
-\r
-class ActionProxyMetaClass(models.Model.__metaclass__):\r
- types = {}\r
-\r
- def __new__(cls, *args, **kwargs):\r
- new_cls = super(ActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)\r
- cls.types[new_cls.get_type()] = new_cls\r
-\r
- class Meta:\r
- proxy = True\r
-\r
- new_cls.Meta = Meta\r
- return new_cls\r
-\r
-class ActionProxy(Action):\r
- __metaclass__ = ActionProxyMetaClass\r
-\r
- def friendly_username(self, viewer, user):\r
- return (viewer == user) and _('You') or user.username\r
-\r
- def friendly_ownername(self, owner, user):\r
- return (owner == user) and _('your') or user.username\r
-\r
- def viewer_or_user_verb(self, viewer, user, viewer_verb, user_verb):\r
- return (viewer == user) and viewer_verb or user_verb \r
-\r
- def hyperlink(self, url, title, **attrs):\r
- return '<a href="%s" %s>%s</a>' % (url, " ".join('%s="%s"' % i for i in attrs.items()), title)\r
-\r
- def describe_node(self, viewer, node):\r
- node_link = self.hyperlink(node.get_absolute_url(), node.headline)\r
-\r
- if node.parent:\r
- node_desc = _("on %(link)s") % {'link': node_link}\r
- else:\r
- node_desc = node_link\r
-\r
- return _("%(user)s %(node_name)s %(node_desc)s") % {\r
- 'user': self.hyperlink(node.author.get_profile_url(), self.friendly_ownername(viewer, node.author)),\r
- 'node_name': node.friendly_name, 'node_desc': node_desc,\r
- }\r
- \r
- class Meta:\r
- proxy = True\r
-\r
-class DummyActionProxyMetaClass(type):\r
- def __new__(cls, *args, **kwargs):\r
- new_cls = super(DummyActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)\r
- ActionProxyMetaClass.types[new_cls.get_type()] = new_cls\r
- return new_cls\r
-\r
-class DummyActionProxy(object):\r
- __metaclass__ = DummyActionProxyMetaClass\r
-\r
- hooks = []\r
-\r
- def process_data(self, **data):\r
- pass\r
-\r
- def process_action(self):\r
- pass\r
-\r
- def save(self, data=None):\r
- self.process_action()\r
-\r
- if data:\r
- self.process_data(**data)\r
-\r
- for hook in self.__class__.hooks:\r
- hook(self, True)\r
-\r
- @classmethod\r
- def get_type(cls):\r
- return re.sub(r'action$', '', cls.__name__.lower())\r
-\r
- @classmethod\r
- def hook(cls, fn):\r
- cls.hooks.append(fn)\r
-\r
-\r
-\r
-class ActionRepute(models.Model):\r
- action = models.ForeignKey(Action, related_name='reputes')\r
- date = models.DateTimeField(default=datetime.datetime.now)\r
- user = models.ForeignKey('User', related_name='reputes')\r
- value = models.IntegerField(default=0)\r
- by_canceled = models.BooleanField(default=False)\r
-\r
- @property\r
- def positive(self):\r
- if self.value > 0: return self.value\r
- return 0\r
-\r
- @property\r
- def negative(self):\r
- if self.value < 0: return self.value\r
- return 0\r
-\r
- def save(self, *args, **kwargs):\r
- super(ActionRepute, self).save(*args, **kwargs)\r
- self.user.reputation = models.F('reputation') + self.value\r
- self.user.save()\r
-\r
- def delete(self):\r
- self.user.reputation = models.F('reputation') - self.value\r
- self.user.save()\r
- super(ActionRepute, self).delete()\r
-\r
- class Meta:\r
- app_label = 'forum'\r
-\r
+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'
+