]> git.openstreetmap.org Git - osqa.git/blob - forum/badges/base.py
more improvements in cache and denormalized data handling
[osqa.git] / forum / badges / base.py
1 import re
2 from string import lower
3
4 from django.contrib.contenttypes.models import ContentType
5 from django.db.models.signals import post_save
6
7 from forum.models.user import activity_record
8 from forum.models.base import denorm_update
9 from forum.models import Badge, Award, Activity, Node
10
11 import logging
12
13 class AbstractBadge(object):
14
15     _instance = None
16
17     @property
18     def name(self):
19         return " ".join(re.findall(r'([A-Z][a-z1-9]+)', re.sub('Badge', '', self.__class__.__name__)))
20
21     @property
22     def description(self):
23         raise NotImplementedError
24
25     def __new__(cls, *args, **kwargs):
26         if cls._instance is None:
27             cls.badge = "-".join(map(lower, re.findall(r'([A-Z][a-z1-9]+)', re.sub('Badge', '', cls.__name__)))) 
28             cls._instance = super(AbstractBadge, cls).__new__(cls, *args, **kwargs)
29
30         return cls._instance
31
32     def install(self):
33         try:
34             installed = Badge.objects.get(slug=self.badge)
35         except:
36             badge = Badge(name=self.name, description=self.description, slug=self.badge, type=self.type)
37             badge.save()
38
39     def award_badge(self, user, obj=None, award_once=False):
40         try:
41             badge = Badge.objects.get(slug=self.badge)
42         except:
43             logging.log('Trying to award a badge not installed in the database.')
44             return
45             
46         content_type = ContentType.objects.get_for_model(obj.__class__)
47
48         awarded = user.awards.filter(badge=badge)
49
50         if not award_once:
51             awarded = awarded.filter(content_type=content_type, object_id=obj.id)
52
53         if len(awarded):
54             logging.log(1, 'Trying to award badged already awarded.')
55             return
56             
57         award = Award(user=user, badge=badge, content_type=content_type, object_id=obj.id)
58         award.save()
59
60 class CountableAbstractBadge(AbstractBadge):
61
62     def __init__(self, model, field, expected_value, handler):
63         def wrapper(instance, sfield, old, new, **kwargs):
64             if sfield == field and (new == expected_value) or (old < expected_value and new > expected_value):
65                 handler(instance=instance)
66         
67         denorm_update.connect(wrapper, sender=model, weak=False)
68
69 class PostCountableAbstractBadge(CountableAbstractBadge):
70     def __init__(self, model, field, expected_value):
71
72         def handler(instance):            
73             self.award_badge(instance.author, instance)
74
75         super(PostCountableAbstractBadge, self).__init__(model, field, expected_value, handler)
76
77 class NodeCountableAbstractBadge(CountableAbstractBadge):
78     def __init__(self, node_type, field, expected_value):
79
80         def handler(instance):
81             if instance.node_type == node_type:
82                 self.award_badge(instance.author, instance)
83
84         super(NodeCountableAbstractBadge, self).__init__(Node, field, expected_value, handler)
85
86 class ActivityAbstractBadge(AbstractBadge):
87
88     def __init__(self, activity_type, handler):
89
90         def wrapper(sender, **kwargs):
91             handler(instance=kwargs['instance'])
92
93         activity_record.connect(wrapper, sender=activity_type, weak=False)
94
95
96 class ActivityCountAbstractBadge(AbstractBadge):
97
98     def __init__(self, activity_type, count):
99
100         def handler(sender, **kwargs):
101             instance = kwargs['instance']
102             if Activity.objects.filter(user=instance.user, activity_type__in=activity_type).count() == count:
103                 self.award_badge(instance.user, instance.content_object)
104
105         if not isinstance(activity_type, (tuple, list)):
106             activity_type = (activity_type, )
107
108         for type in activity_type:
109             activity_record.connect(handler, sender=type, weak=False)
110
111 class FirstActivityAbstractBadge(ActivityCountAbstractBadge):
112
113     def __init__(self, activity_type):
114         super(FirstActivityAbstractBadge, self).__init__(activity_type, 1)