]> git.openstreetmap.org Git - osqa.git/blob - forum/models/action.py
OSQA-829, resolves a unicode problem when answering question, use smart_unicode for...
[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             self.process_action()
139             self.trigger_hooks(threaded, True)
140
141         return self
142
143     def delete(self, *args, **kwargs):
144         self.cancel_action()
145         super(Action, self).delete(*args, **kwargs)
146
147     def cancel(self, user=None, ip=None):
148         if not self.canceled:
149             self.canceled = True
150             self.canceled_at = datetime.datetime.now()
151             self.canceled_by = (user is None) and self.user or user
152             if ip:
153                 self.canceled_ip = ip
154             self.save()
155             self.cancel_reputes()
156             self.cancel_action()
157         #self.trigger_hooks(False)
158
159     @classmethod
160     def get_current(cls, **kwargs):
161         kwargs['canceled'] = False
162
163         try:
164             return cls.objects.get(**kwargs)
165         except cls.MultipleObjectsReturned:
166             logging.error("Got multiple values for action %s with args %s", cls.__name__,
167                           ", ".join(["%s='%s'" % i for i in kwargs.items()]))
168             raise
169         except cls.DoesNotExist:
170             return None
171
172     @classmethod
173     def hook(cls, fn):
174         if not Action.hooks.get(cls, None):
175             Action.hooks[cls] = []
176
177         Action.hooks[cls].append(fn)
178
179     def trigger_hooks(self, threaded, new=True):
180         if threaded:
181             thread = Thread(target=trigger_hooks, args=[self, Action.hooks, new])
182             thread.setDaemon(True)
183             thread.start()
184         else:
185             trigger_hooks(self, Action.hooks, new)
186
187     class Meta:
188         app_label = 'forum'
189
190 def trigger_hooks(action, hooks, new):
191     for cls, hooklist in hooks.items():
192         if isinstance(action, cls):
193             for hook in hooklist:
194                 try:
195                     hook(action=action, new=new)
196                 except Exception, e:
197                     import traceback
198                     logging.error("Error in %s hook: %s" % (cls.__name__, str(e)))
199                     logging.error(traceback.format_exc())
200
201 class ActionProxyMetaClass(BaseMetaClass):
202     types = {}
203
204     def __new__(cls, *args, **kwargs):
205         new_cls = super(ActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)
206         cls.types[new_cls.get_type()] = new_cls
207
208         class Meta:
209             proxy = True
210
211         new_cls.Meta = Meta
212         return new_cls
213
214 class ActionProxy(Action):
215     __metaclass__ = ActionProxyMetaClass
216
217     def friendly_username(self, viewer, user):
218         return (viewer == user) and _('You') or smart_unicode(user.username)
219
220     def friendly_ownername(self, owner, user):
221         return (owner == user) and _('your') or smart_unicode(user.username)
222
223     def viewer_or_user_verb(self, viewer, user, viewer_verb, user_verb):
224         return (viewer == user) and viewer_verb or user_verb
225
226     def hyperlink(self, url, title, **attrs):
227         return html.hyperlink(url, title, **attrs)
228
229     def describe_node(self, viewer, node):
230         node_link = self.hyperlink(node.get_absolute_url(), node.headline)
231
232         if node.parent:
233             node_desc = _("on %(link)s") % {'link': node_link}
234         else:
235             node_desc = node_link
236
237         return _("%(user)s %(node_name)s %(node_desc)s") % {
238         'user': self.hyperlink(node.author.get_profile_url(), self.friendly_ownername(viewer, node.author)),
239         'node_name': node.friendly_name,
240         'node_desc': node_desc,
241         }
242
243     def affected_links(self, viewer):
244         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()])])
245
246     class Meta:
247         proxy = True
248
249 class DummyActionProxyMetaClass(type):
250     def __new__(cls, *args, **kwargs):
251         new_cls = super(DummyActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)
252         ActionProxyMetaClass.types[new_cls.get_type()] = new_cls
253         return new_cls
254
255 class DummyActionProxy(object):
256     __metaclass__ = DummyActionProxyMetaClass
257
258     hooks = []
259
260     def __init__(self, ip=None):
261         self.ip = ip
262
263     def process_data(self, **data):
264         pass
265
266     def process_action(self):
267         pass
268
269     def save(self, data=None):
270         self.process_action()
271
272         if data:
273             self.process_data(**data)
274
275         for hook in self.__class__.hooks:
276             hook(self, True)
277
278     @classmethod
279     def get_type(cls):
280         return re.sub(r'action$', '', cls.__name__.lower())
281
282     @classmethod
283     def hook(cls, fn):
284         cls.hooks.append(fn)
285
286
287 class ActionRepute(models.Model):
288     action = models.ForeignKey(Action, related_name='reputes')
289     date = models.DateTimeField(default=datetime.datetime.now)
290     user = models.ForeignKey('User', related_name='reputes')
291     value = models.IntegerField(default=0)
292     by_canceled = models.BooleanField(default=False)
293
294     @property
295     def positive(self):
296         if self.value > 0: return self.value
297         return 0
298
299     @property
300     def negative(self):
301         if self.value < 0: return self.value
302         return 0
303
304     def _add_to_rep(self, value):
305         if int(self.user.reputation + value) < 1 and not settings.ALLOW_NEGATIVE_REPUTATION:
306             return 0
307         else:
308             return models.F('reputation') + value
309
310     def save(self, *args, **kwargs):
311         super(ActionRepute, self).save(*args, **kwargs)
312         self.user.reputation = self._add_to_rep(self.value)
313         self.user.save()
314
315     def delete(self):
316         self.user.reputation = self._add_to_rep(-self.value)
317         self.user.save()
318         super(ActionRepute, self).delete()
319
320     class Meta:
321         app_label = 'forum'
322