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