]> git.openstreetmap.org Git - osqa.git/blob - forum/models/action.py
7f9a7c189acf73817d9f64d72969b919ace2e072
[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                     logging.error("Error in %s hook: %s" % (cls.__name__, str(e)))\r
185 \r
186 class ActionProxyMetaClass(BaseMetaClass):\r
187     types = {}\r
188 \r
189     def __new__(cls, *args, **kwargs):\r
190         new_cls = super(ActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)\r
191         cls.types[new_cls.get_type()] = new_cls\r
192 \r
193         class Meta:\r
194             proxy = True\r
195 \r
196         new_cls.Meta = Meta\r
197         return new_cls\r
198 \r
199 class ActionProxy(Action):\r
200     __metaclass__ = ActionProxyMetaClass\r
201 \r
202     def friendly_username(self, viewer, user):\r
203         return (viewer == user) and _('You') or user.username\r
204 \r
205     def friendly_ownername(self, owner, user):\r
206         return (owner == user) and _('your') or user.username\r
207 \r
208     def viewer_or_user_verb(self, viewer, user, viewer_verb, user_verb):\r
209         return (viewer == user) and viewer_verb or user_verb    \r
210 \r
211     def hyperlink(self, url, title, **attrs):\r
212         return '<a href="%s" %s>%s</a>' % (url, " ".join('%s="%s"' % i for i in attrs.items()), title)\r
213 \r
214     def describe_node(self, viewer, node):\r
215         node_link = self.hyperlink(node.get_absolute_url(), node.headline)\r
216 \r
217         if node.parent:\r
218             node_desc = _("on %(link)s") % {'link': node_link}\r
219         else:\r
220             node_desc = node_link\r
221 \r
222         return _("%(user)s %(node_name)s %(node_desc)s") % {\r
223             'user': self.hyperlink(node.author.get_profile_url(), self.friendly_ownername(viewer, node.author)),\r
224             'node_name': node.friendly_name, 'node_desc': node_desc,\r
225         }\r
226     \r
227     class Meta:\r
228         proxy = True\r
229 \r
230 class DummyActionProxyMetaClass(type):\r
231     def __new__(cls, *args, **kwargs):\r
232         new_cls = super(DummyActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)\r
233         ActionProxyMetaClass.types[new_cls.get_type()] = new_cls\r
234         return new_cls\r
235 \r
236 class DummyActionProxy(object):\r
237     __metaclass__ = DummyActionProxyMetaClass\r
238 \r
239     hooks = []\r
240 \r
241     def __init__(self, ip=None):\r
242         self.ip = ip\r
243 \r
244     def process_data(self, **data):\r
245         pass\r
246 \r
247     def process_action(self):\r
248         pass\r
249 \r
250     def save(self, data=None):\r
251         self.process_action()\r
252 \r
253         if data:\r
254             self.process_data(**data)\r
255 \r
256         for hook in self.__class__.hooks:\r
257             hook(self, True)\r
258 \r
259     @classmethod\r
260     def get_type(cls):\r
261         return re.sub(r'action$', '', cls.__name__.lower())\r
262 \r
263     @classmethod\r
264     def hook(cls, fn):\r
265         cls.hooks.append(fn)\r
266 \r
267 \r
268 \r
269 class ActionRepute(models.Model):\r
270     action = models.ForeignKey(Action, related_name='reputes')\r
271     date = models.DateTimeField(default=datetime.datetime.now)\r
272     user = models.ForeignKey('User', related_name='reputes')\r
273     value = models.IntegerField(default=0)\r
274     by_canceled = models.BooleanField(default=False)\r
275 \r
276     @property\r
277     def positive(self):\r
278         if self.value > 0: return self.value\r
279         return 0\r
280 \r
281     @property\r
282     def negative(self):\r
283         if self.value < 0: return self.value\r
284         return 0\r
285 \r
286     def save(self, *args, **kwargs):\r
287         super(ActionRepute, self).save(*args, **kwargs)\r
288         self.user.reputation += self.value\r
289         self.user.save()\r
290 \r
291     def delete(self):\r
292         self.user.reputation -= self.value\r
293         self.user.save()\r
294         super(ActionRepute, self).delete()\r
295 \r
296     class Meta:\r
297         app_label = 'forum'\r
298 \r