]> git.openstreetmap.org Git - osqa.git/blobdiff - forum/badges/base.py
fix breach in award points that allows user to award infinite points
[osqa.git] / forum / badges / base.py
index c3e9fcb85cd491f5c80b7f3698ca160da7628e30..c78a925e80685242a3b903752c1a63e789bea651 100644 (file)
 import re
 from string import lower
 
-from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import MultipleObjectsReturned
 from django.db.models.signals import post_save
 
-from forum.models.user import activity_record
-from forum.models import Badge, Award, Activity
+from forum.models import Badge, Node, Action
+from forum.actions import AwardAction
 
 import logging
 
-class AbstractBadge(object):
-
-    _instance = None
-
-    @property
-    def name(self):
-        return " ".join(re.findall(r'([A-Z][a-z1-9]+)', re.sub('Badge', '', self.__class__.__name__)))
-
-    @property
-    def description(self):
-        raise NotImplementedError
-
-    def __new__(cls, *args, **kwargs):
-        if cls._instance is None:
-            cls.badge = "-".join(map(lower, re.findall(r'([A-Z][a-z1-9]+)', re.sub('Badge', '', cls.__name__)))) 
-            cls._instance = super(AbstractBadge, cls).__new__(cls, *args, **kwargs)
-
-        return cls._instance
-
-    def install(self):
-        try:
-            installed = Badge.objects.get(slug=self.badge)
-        except:
-            badge = Badge(name=self.name, description=self.description, slug=self.badge, type=self.type)
-            badge.save()
-
-    def award_badge(self, user, obj=None, award_once=False):
-        try:
-            badge = Badge.objects.get(slug=self.badge)
-        except:
-            logging.log('Trying to award a badge not installed in the database.')
-            return
-            
-        content_type = ContentType.objects.get_for_model(obj.__class__)
+installed = dict([(b.cls, b) for b in Badge.objects.all()])
 
-        awarded = user.awards.filter(badge=badge)
+class BadgesMeta(type):
+    by_class = {}
+    by_id = {}
 
-        if not award_once:
-            awarded = awarded.filter(content_type=content_type, object_id=obj.id)
+    def __new__(mcs, name, bases, dic):
+        badge = type.__new__(mcs, name, bases, dic)
 
-        if len(awarded):
-            logging.log(1, 'Trying to award badged already awarded.')
-            return
-            
-        award = Award(user=user, badge=badge, content_type=content_type, object_id=obj.id)
-        award.save()
+        if not dic.get('abstract', False):
+            if not name in installed:
+                ondb = Badge(cls=name, type=dic.get('type', Badge.BRONZE))
+                ondb.save()
+            else:
+                ondb = installed[name]
 
-class CountableAbstractBadge(AbstractBadge):
+            badge.ondb = ondb.id
 
-    def __init__(self, model, field, expected_value, handler):
-        def wrapper(instance, **kwargs):
-            dirty_fields = instance.get_dirty_fields()
-            if field in dirty_fields and instance.__dict__[field] == expected_value:
-                handler(instance=instance)
-        
-        post_save.connect(wrapper, sender=model, weak=False)
+            inst = badge()
 
-class PostCountableAbstractBadge(CountableAbstractBadge):
-    def __init__(self, model, field, expected_value):
+            def hook(action, new):
+                user = inst.award_to(action)
 
-        def handler(instance):            
-            self.award_badge(instance.author, instance)
+                if user:
+                    badge.award(user, action, badge.award_once)
 
-        super(PostCountableAbstractBadge, self).__init__(model, field, expected_value, handler)
+            for action in badge.listen_to:
+                action.hook(hook)
 
+            BadgesMeta.by_class[name] = inst
+            BadgesMeta.by_id[ondb.id] = inst
 
-class ActivityAbstractBadge(AbstractBadge):
+        return badge
 
-    def __init__(self, activity_type, handler):
-
-        def wrapper(sender, **kwargs):
-            handler(instance=kwargs['instance'])
-
-        activity_record.connect(wrapper, sender=activity_type, weak=False)
-
-
-class ActivityCountAbstractBadge(AbstractBadge):
-
-    def __init__(self, activity_type, count):
-
-        def handler(sender, **kwargs):
-            instance = kwargs['instance']
-            if Activity.objects.filter(user=instance.user, activity_type__in=activity_type).count() == count:
-                self.award_badge(instance.user, instance.content_object)
+class AbstractBadge(object):
+    __metaclass__ = BadgesMeta
 
-        if not isinstance(activity_type, (tuple, list)):
-            activity_type = (activity_type, )
+    abstract = True
+    award_once = False
 
-        for type in activity_type:
-            activity_record.connect(handler, sender=type, weak=False)
+    @property
+    def name(self):
+        raise NotImplementedError
 
-class FirstActivityAbstractBadge(ActivityCountAbstractBadge):
+    @property
+    def description(self):
+        raise NotImplementedError
 
-    def __init__(self, activity_type):
-        super(FirstActivityAbstractBadge, self).__init__(activity_type, 1)
+    @classmethod
+    def award(cls, user, action, once=False):
+        db_object = Badge.objects.get(id=cls.ondb)
+        try:
+            if once:
+                node = None
+                awarded = AwardAction.get_for(user, db_object)
+            else:
+                node = action.node
+                awarded = AwardAction.get_for(user, db_object, node)
+
+            trigger = isinstance(action, Action) and action or None
+
+            if not awarded:
+                AwardAction(user=user, node=node).save(data=dict(badge=db_object, trigger=trigger))
+        except MultipleObjectsReturned:
+            if node:
+                logging.error('Found multiple %s badges awarded for user %s (%s)' % (self.name, user.username, user.id))
+            else:
+                logging.error('Found multiple %s badges awarded for user %s (%s) and node %s' % (self.name, user.username, user.id, node.id))
\ No newline at end of file