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