]> git.openstreetmap.org Git - osqa.git/blob - forum/models/action.py
9a6f5ae43b19971ed37d823b28721573fc2efd83
[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     @property\r
69     def verb(self):\r
70         return ""\r
71 \r
72     def describe(self, viewer=None):\r
73         return ""\r
74 \r
75     def get_absolute_url(self):\r
76         if self.node:\r
77             return self.node.get_absolute_url()\r
78         else:\r
79             return self.user.get_profile_url()\r
80 \r
81     def repute(self, user, value):\r
82         repute = ActionRepute(action=self, user=user, value=value)\r
83         repute.save()\r
84         return repute\r
85 \r
86     def cancel_reputes(self):\r
87         for repute in self.reputes.all():\r
88             cancel = ActionRepute(action=self, user=repute.user, value=(-repute.value), by_canceled=True)\r
89             cancel.save()\r
90 \r
91     def leaf(self):\r
92         leaf_cls = ActionProxyMetaClass.types.get(self.action_type, None)\r
93 \r
94         if leaf_cls is None:\r
95             return self\r
96 \r
97         leaf = leaf_cls()\r
98         leaf.__dict__ = self.__dict__\r
99         return leaf\r
100 \r
101     @classmethod\r
102     def get_type(cls):\r
103         return re.sub(r'action$', '', cls.__name__.lower())\r
104 \r
105     def save(self, data=None, *args, **kwargs):\r
106         isnew = False\r
107 \r
108         if not self.id:\r
109             self.action_type = self.__class__.get_type()\r
110             isnew = True\r
111 \r
112         if data:\r
113             self.process_data(**data)\r
114 \r
115         super(Action, self).save(*args, **kwargs)\r
116 \r
117         if isnew:\r
118             if (self.node is None) or (not self.node.wiki):\r
119                 self.repute_users()\r
120             self.process_action()\r
121             self.trigger_hooks(True)\r
122 \r
123         return self\r
124 \r
125     def delete(self):\r
126         self.cancel_action()\r
127         super(Action, self).delete()\r
128 \r
129     def cancel(self, user=None, ip=None):\r
130         if not self.canceled:\r
131             self.canceled = True\r
132             self.canceled_at = datetime.datetime.now()\r
133             self.canceled_by = (user is None) and self.user or user\r
134             if ip:\r
135                 self.canceled_ip = ip\r
136             self.save()\r
137             self.cancel_reputes()\r
138             self.cancel_action()\r
139             #self.trigger_hooks(False)\r
140 \r
141     @classmethod\r
142     def get_current(cls, **kwargs):\r
143         kwargs['canceled'] = False\r
144 \r
145         try:\r
146             return cls.objects.get(**kwargs)\r
147         except cls.MultipleObjectsReturned:\r
148             logging.error("Got multiple values for action %s with args %s", cls.__name__,\r
149                           ", ".join(["%s='%s'" % i for i in kwargs.items()]))\r
150             raise\r
151         except cls.DoesNotExist:\r
152             return None\r
153 \r
154     @classmethod\r
155     def hook(cls, fn):\r
156         if not Action.hooks.get(cls, None):\r
157             Action.hooks[cls] = []\r
158 \r
159         Action.hooks[cls].append(fn)\r
160 \r
161     def trigger_hooks(self, new=True):\r
162         thread = Thread(target=trigger_hooks_threaded,  args=[self, Action.hooks, new])\r
163         thread.setDaemon(True)\r
164         thread.start()\r
165 \r
166     class Meta:\r
167         app_label = 'forum'\r
168 \r
169 def trigger_hooks_threaded(action, hooks, new):\r
170     for cls, hooklist in hooks.items():\r
171         if isinstance(action, cls):\r
172             for hook in hooklist:\r
173                 try:\r
174                     hook(action=action, new=new)\r
175                 except Exception, e:\r
176                     logging.error("Error in %s hook: %s" % (cls.__name__, str(e)))\r
177 \r
178 class ActionProxyMetaClass(models.Model.__metaclass__):\r
179     types = {}\r
180 \r
181     def __new__(cls, *args, **kwargs):\r
182         new_cls = super(ActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)\r
183         cls.types[new_cls.get_type()] = new_cls\r
184 \r
185         class Meta:\r
186             proxy = True\r
187 \r
188         new_cls.Meta = Meta\r
189         return new_cls\r
190 \r
191 class ActionProxy(Action):\r
192     __metaclass__ = ActionProxyMetaClass\r
193 \r
194     def friendly_username(self, viewer, user):\r
195         return (viewer == user) and _('You') or user.username\r
196 \r
197     def friendly_ownername(self, owner, user):\r
198         return (owner == user) and _('your') or user.username\r
199 \r
200     def viewer_or_user_verb(self, viewer, user, viewer_verb, user_verb):\r
201         return (viewer == user) and viewer_verb or user_verb    \r
202 \r
203     def hyperlink(self, url, title, **attrs):\r
204         return '<a href="%s" %s>%s</a>' % (url, " ".join('%s="%s"' % i for i in attrs.items()), title)\r
205 \r
206     def describe_node(self, viewer, node):\r
207         node_link = self.hyperlink(node.get_absolute_url(), node.headline)\r
208 \r
209         if node.parent:\r
210             node_desc = _("on %(link)s") % {'link': node_link}\r
211         else:\r
212             node_desc = node_link\r
213 \r
214         return _("%(user)s %(node_name)s %(node_desc)s") % {\r
215             'user': self.hyperlink(node.author.get_profile_url(), self.friendly_ownername(viewer, node.author)),\r
216             'node_name': node.friendly_name, 'node_desc': node_desc,\r
217         }\r
218     \r
219     class Meta:\r
220         proxy = True\r
221 \r
222 class DummyActionProxyMetaClass(type):\r
223     def __new__(cls, *args, **kwargs):\r
224         new_cls = super(DummyActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)\r
225         ActionProxyMetaClass.types[new_cls.get_type()] = new_cls\r
226         return new_cls\r
227 \r
228 class DummyActionProxy(object):\r
229     __metaclass__ = DummyActionProxyMetaClass\r
230 \r
231     hooks = []\r
232 \r
233     def __init__(self, ip=None):\r
234         self.ip = ip\r
235 \r
236     def process_data(self, **data):\r
237         pass\r
238 \r
239     def process_action(self):\r
240         pass\r
241 \r
242     def save(self, data=None):\r
243         self.process_action()\r
244 \r
245         if data:\r
246             self.process_data(**data)\r
247 \r
248         for hook in self.__class__.hooks:\r
249             hook(self, True)\r
250 \r
251     @classmethod\r
252     def get_type(cls):\r
253         return re.sub(r'action$', '', cls.__name__.lower())\r
254 \r
255     @classmethod\r
256     def hook(cls, fn):\r
257         cls.hooks.append(fn)\r
258 \r
259 \r
260 \r
261 class ActionRepute(models.Model):\r
262     action = models.ForeignKey(Action, related_name='reputes')\r
263     date = models.DateTimeField(default=datetime.datetime.now)\r
264     user = models.ForeignKey('User', related_name='reputes')\r
265     value = models.IntegerField(default=0)\r
266     by_canceled = models.BooleanField(default=False)\r
267 \r
268     @property\r
269     def positive(self):\r
270         if self.value > 0: return self.value\r
271         return 0\r
272 \r
273     @property\r
274     def negative(self):\r
275         if self.value < 0: return self.value\r
276         return 0\r
277 \r
278     def save(self, *args, **kwargs):\r
279         super(ActionRepute, self).save(*args, **kwargs)\r
280         self.user.reputation = models.F('reputation') + self.value\r
281         self.user.save()\r
282 \r
283     def delete(self):\r
284         self.user.reputation = models.F('reputation') - self.value\r
285         self.user.save()\r
286         super(ActionRepute, self).delete()\r
287 \r
288     class Meta:\r
289         app_label = 'forum'\r
290 \r