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