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