]> git.openstreetmap.org Git - osqa.git/blob - forum/models/action.py
Fixed http://jira.osqa.net/browse/OSQA-216.
[osqa.git] / forum / models / action.py
1 from django.utils.translation import ugettext as _\r
2 from utils import PickledObjectField\r
3 from threading import Thread\r
4 from base import *\r
5 import re\r
6 \r
7 class ActionManager(models.Manager):\r
8     use_for_related_fields = True\r
9 \r
10     def get_query_set(self):\r
11         qs = super(ActionManager, self).get_query_set().filter(canceled=False)\r
12 \r
13         if self.model is not Action:\r
14             return qs.filter(action_type=self.model.get_type())\r
15         else:\r
16             return qs\r
17 \r
18     def get(self, *args, **kwargs):\r
19         action = super(ActionManager, self).get(*args, **kwargs)\r
20         if self.model == Action:\r
21             return action.leaf()\r
22         return action\r
23 \r
24     def get_for_types(self, types, *args, **kwargs):\r
25         kwargs['action_type__in'] = [t.get_type() for t in types]\r
26         return self.get(*args, **kwargs)\r
27 \r
28         \r
29 \r
30 class Action(models.Model):\r
31     user = models.ForeignKey('User', related_name="actions")\r
32     ip   = models.CharField(max_length=16)\r
33     node = models.ForeignKey('Node', null=True, related_name="actions")\r
34     action_type = models.CharField(max_length=16)\r
35     action_date = models.DateTimeField(default=datetime.datetime.now)\r
36 \r
37     extra = PickledObjectField()\r
38 \r
39     canceled = models.BooleanField(default=False)\r
40     canceled_by = models.ForeignKey('User', null=True, related_name="canceled_actions")\r
41     canceled_at = models.DateTimeField(null=True)\r
42     canceled_ip = models.CharField(max_length=16)\r
43 \r
44     hooks = {}\r
45 \r
46     objects = ActionManager()\r
47 \r
48     @property\r
49     def at(self):\r
50         return self.action_date\r
51 \r
52     @property\r
53     def by(self):\r
54         return self.user\r
55 \r
56     def repute_users(self):\r
57         pass\r
58 \r
59     def process_data(self, **data):\r
60         pass\r
61 \r
62     def process_action(self):\r
63         pass\r
64 \r
65     def cancel_action(self):\r
66         pass\r
67 \r
68     def describe(self, viewer=None):\r
69         return ""\r
70 \r
71     def repute(self, user, value):\r
72         repute = ActionRepute(action=self, user=user, value=value)\r
73         repute.save()\r
74         return repute\r
75 \r
76     def cancel_reputes(self):\r
77         for repute in self.reputes.all():\r
78             cancel = ActionRepute(action=self, user=repute.user, value=(-repute.value), by_canceled=True)\r
79             cancel.save()\r
80 \r
81     def leaf(self):\r
82         leaf_cls = ActionProxyMetaClass.types.get(self.action_type, None)\r
83 \r
84         if leaf_cls is None:\r
85             return self\r
86 \r
87         leaf = leaf_cls()\r
88         leaf.__dict__ = self.__dict__\r
89         return leaf\r
90 \r
91     @classmethod\r
92     def get_type(cls):\r
93         return re.sub(r'action$', '', cls.__name__.lower())\r
94 \r
95     def save(self, data=None, *args, **kwargs):\r
96         isnew = False\r
97 \r
98         if not self.id:\r
99             self.action_type = self.__class__.get_type()\r
100             isnew = True\r
101 \r
102         if data:\r
103             self.process_data(**data)\r
104 \r
105         super(Action, self).save(*args, **kwargs)\r
106 \r
107         if isnew:\r
108             if (self.node is None) or (not self.node.wiki):\r
109                 self.repute_users()\r
110             self.process_action()\r
111             self.trigger_hooks(True)\r
112 \r
113         return self\r
114 \r
115     def delete(self):\r
116         self.cancel_action()\r
117         super(Action, self).delete()\r
118 \r
119     def cancel(self, user=None, ip=None):\r
120         if not self.canceled:\r
121             self.canceled = True\r
122             self.canceled_at = datetime.datetime.now()\r
123             self.canceled_by = (user is None) and self.user or user\r
124             if ip:\r
125                 self.canceled_ip = ip\r
126             self.save()\r
127             self.cancel_reputes()\r
128             self.cancel_action()\r
129             #self.trigger_hooks(False)\r
130 \r
131     @classmethod\r
132     def get_current(cls, **kwargs):\r
133         kwargs['canceled'] = False\r
134 \r
135         try:\r
136             return cls.objects.get(**kwargs)\r
137         except cls.MultipleObjectsReturned:\r
138             logging.error("Got multiple values for action %s with args %s", cls.__name__,\r
139                           ", ".join(["%s='%s'" % i for i in kwargs.items()]))\r
140             raise\r
141         except cls.DoesNotExist:\r
142             return None\r
143 \r
144     @classmethod\r
145     def hook(cls, fn):\r
146         if not Action.hooks.get(cls, None):\r
147             Action.hooks[cls] = []\r
148 \r
149         Action.hooks[cls].append(fn)\r
150 \r
151     def trigger_hooks(self, new=True):\r
152         thread = Thread(target=trigger_hooks_threaded,  args=[self, Action.hooks, new])\r
153         thread.setDaemon(True)\r
154         thread.start()\r
155 \r
156     class Meta:\r
157         app_label = 'forum'\r
158 \r
159 def trigger_hooks_threaded(action, hooks, new):\r
160     for cls, hooklist in hooks.items():\r
161         if isinstance(action, cls):\r
162             for hook in hooklist:\r
163                 try:\r
164                     hook(action=action, new=new)\r
165                 except Exception, e:\r
166                     logging.error("Error in %s hook: %s" % (cls.__name__, str(e)))\r
167 \r
168 class ActionProxyMetaClass(models.Model.__metaclass__):\r
169     types = {}\r
170 \r
171     def __new__(cls, *args, **kwargs):\r
172         new_cls = super(ActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)\r
173         cls.types[new_cls.get_type()] = new_cls\r
174 \r
175         class Meta:\r
176             proxy = True\r
177 \r
178         new_cls.Meta = Meta\r
179         return new_cls\r
180 \r
181 class ActionProxy(Action):\r
182     __metaclass__ = ActionProxyMetaClass\r
183 \r
184     def friendly_username(self, viewer, user):\r
185         return (viewer == user) and _('You') or user.username\r
186 \r
187     def friendly_ownername(self, owner, user):\r
188         return (owner == user) and _('your') or user.username\r
189 \r
190     def viewer_or_user_verb(self, viewer, user, viewer_verb, user_verb):\r
191         return (viewer == user) and viewer_verb or user_verb    \r
192 \r
193     def hyperlink(self, url, title, **attrs):\r
194         return '<a href="%s" %s>%s</a>' % (url, " ".join('%s="%s"' % i for i in attrs.items()), title)\r
195 \r
196     def describe_node(self, viewer, node):\r
197         node_link = self.hyperlink(node.get_absolute_url(), node.headline)\r
198 \r
199         if node.parent:\r
200             node_desc = _("on %(link)s") % {'link': node_link}\r
201         else:\r
202             node_desc = node_link\r
203 \r
204         return _("%(user)s %(node_name)s %(node_desc)s") % {\r
205             'user': self.hyperlink(node.author.get_profile_url(), self.friendly_ownername(viewer, node.author)),\r
206             'node_name': node.friendly_name, 'node_desc': node_desc,\r
207         }\r
208     \r
209     class Meta:\r
210         proxy = True\r
211 \r
212 class DummyActionProxy(Action):\r
213     __metaclass__ = ActionProxyMetaClass\r
214 \r
215     hooks = []\r
216 \r
217     def process_data(self, **data):\r
218         pass\r
219 \r
220     def process_action(self):\r
221         pass\r
222 \r
223     def save(self, data=None):\r
224         self.process_action()\r
225 \r
226         if data:\r
227             self.process_data(**data)\r
228 \r
229         for hook in self.__class__.hooks:\r
230             hook(self, True)\r
231 \r
232     @classmethod\r
233     def get_type(cls):\r
234         return re.sub(r'action$', '', cls.__name__.lower())\r
235 \r
236     @classmethod\r
237     def hook(cls, fn):\r
238         cls.hooks.append(fn)\r
239 \r
240 \r
241 \r
242 class ActionRepute(models.Model):\r
243     action = models.ForeignKey(Action, related_name='reputes')\r
244     date = models.DateTimeField(default=datetime.datetime.now)\r
245     user = models.ForeignKey('User', related_name='reputes')\r
246     value = models.IntegerField(default=0)\r
247     by_canceled = models.BooleanField(default=False)\r
248 \r
249     @property\r
250     def positive(self):\r
251         if self.value > 0: return self.value\r
252         return 0\r
253 \r
254     @property\r
255     def negative(self):\r
256         if self.value < 0: return self.value\r
257         return 0\r
258 \r
259     def save(self, *args, **kwargs):\r
260         super(ActionRepute, self).save(*args, **kwargs)\r
261         self.user.reputation = models.F('reputation') + self.value\r
262         self.user.save()\r
263 \r
264     def delete(self):\r
265         self.user.reputation = models.F('reputation') - self.value\r
266         self.user.save()\r
267         super(ActionRepute, self).delete()\r
268 \r
269     class Meta:\r
270         app_label = 'forum'\r
271 \r