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