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.base import denorm_update
-from forum.models import Badge, Award, Activity, Node
+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(1, 'Trying to award a badge not installed in the database.')
- return
-
- content_type = ContentType.objects.get_for_model(obj.__class__)
-
- awarded = user.awards.filter(badge=badge)
+installed = dict([(b.cls, b) for b in Badge.objects.all()])
- if not award_once:
- awarded = awarded.filter(content_type=content_type, object_id=obj.id)
+class BadgesMeta(type):
+ by_class = {}
+ by_id = {}
- 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()
+ def __new__(mcs, name, bases, dic):
+ badge = type.__new__(mcs, name, bases, dic)
-class CountableAbstractBadge(AbstractBadge):
+ 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]
- def __init__(self, model, field, expected_value, handler):
- def wrapper(instance, sfield, old, new, **kwargs):
- if sfield == field and (new == expected_value) or (old < expected_value and new > expected_value):
- handler(instance=instance)
-
- denorm_update.connect(wrapper, sender=model, weak=False)
+ badge.ondb = ondb.id
-class PostCountableAbstractBadge(CountableAbstractBadge):
- def __init__(self, model, field, expected_value):
+ inst = badge()
- def handler(instance):
- self.award_badge(instance.author, instance)
+ def hook(action, new):
+ user = inst.award_to(action)
- super(PostCountableAbstractBadge, self).__init__(model, field, expected_value, handler)
+ if user:
+ badge.award(user, action, badge.award_once)
-class NodeCountableAbstractBadge(CountableAbstractBadge):
- def __init__(self, node_type, field, expected_value):
+ for action in badge.listen_to:
+ action.hook(hook)
- def handler(instance):
- if instance.node_type == node_type:
- self.award_badge(instance.author, instance)
+ BadgesMeta.by_class[name] = inst
+ BadgesMeta.by_id[ondb.id] = inst
- super(NodeCountableAbstractBadge, self).__init__(Node, field, expected_value, handler)
+ return badge
-class ActivityAbstractBadge(AbstractBadge):
-
- 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