import datetime from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic from django.contrib.auth.models import User from django.db import models from django.utils.translation import ugettext as _ from django.utils.html import escape from askbot import const from django.core.urlresolvers import reverse class VoteManager(models.Manager): def get_up_vote_count_from_user(self, user): if user is not None: return self.filter(user=user, vote=1).count() else: return 0 def get_down_vote_count_from_user(self, user): if user is not None: return self.filter(user=user, vote=-1).count() else: return 0 def get_votes_count_today_from_user(self, user): if user is not None: today = datetime.date.today() return self.filter(user=user, voted_at__range=(today, today + datetime.timedelta(1))).count() else: return 0 class Vote(models.Model): VOTE_UP = +1 VOTE_DOWN = -1 VOTE_CHOICES = ( (VOTE_UP, u'Up'), (VOTE_DOWN, u'Down'), ) user = models.ForeignKey('auth.User', related_name='votes') voted_post = models.ForeignKey('Post', related_name='votes') vote = models.SmallIntegerField(choices=VOTE_CHOICES) voted_at = models.DateTimeField(default=datetime.datetime.now) objects = VoteManager() class Meta: unique_together = ('user', 'voted_post') app_label = 'askbot' db_table = u'vote' def __unicode__(self): return '[%s] voted at %s: %s' %(self.user, self.voted_at, self.vote) def __int__(self): """1 if upvote -1 if downvote""" return self.vote def is_upvote(self): return self.vote == self.VOTE_UP def is_downvote(self): return self.vote == self.VOTE_DOWN def is_opposite(self, vote_type): assert(vote_type in (self.VOTE_UP, self.VOTE_DOWN)) return self.vote != vote_type def cancel(self): """cancel the vote while taking into account whether vote was up or down return change in score on the post """ #importing locally because of circular dependency from askbot import auth score_before = self.voted_post.points if self.vote > 0: # cancel upvote auth.onUpVotedCanceled(self, self.voted_post, self.user) else: # cancel downvote auth.onDownVotedCanceled(self, self.voted_post, self.user) score_after = self.voted_post.points return score_after - score_before class BadgeData(models.Model): """Awarded for notable actions performed on the site by Users.""" slug = models.SlugField(max_length=50, unique=True) awarded_count = models.PositiveIntegerField(default=0) awarded_to = models.ManyToManyField( User, through='Award', related_name='badges' ) def _get_meta_data(self): """retrieves badge metadata stored in a file""" from askbot.models import badges return badges.get_badge(self.slug) def get_name(self): return self._get_meta_data().name def get_description(self): return self._get_meta_data().description def get_css_class(self): return self._get_meta_data().css_class def get_type_display(self): #todo - rename "type" -> "level" in this model return self._get_meta_data().get_level_display() class Meta: app_label = 'askbot' ordering = ('slug',) def __unicode__(self): return u'%s: %s' % (self.get_type_display(), self.slug) def get_absolute_url(self): return '%s%s/' % (reverse('badge', args=[self.id]), self.slug) class Award(models.Model): """The awarding of a Badge to a User.""" user = models.ForeignKey(User, related_name='award_user') badge = models.ForeignKey(BadgeData, related_name='award_badge') content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey('content_type', 'object_id') awarded_at = models.DateTimeField(default=datetime.datetime.now) notified = models.BooleanField(default=False) def __unicode__(self): return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.get_name(), self.awarded_at) class Meta: app_label = 'askbot' db_table = u'award' class ReputeManager(models.Manager): def get_reputation_by_upvoted_today(self, user): """ For one user in one day, he can only earn rep till certain score (ep. +200) by upvoted(also subtracted from upvoted canceled). This is because we need to prohibit gaming system by upvoting/cancel again and again. """ if user is None: return 0 else: today = datetime.date.today() tomorrow = today + datetime.timedelta(1) rep_types = (1,-8) sums = self.filter(models.Q(reputation_type__in=rep_types), user=user, reputed_at__range=(today, tomorrow), ).aggregate(models.Sum('positive'), models.Sum('negative')) if sums: pos = sums['positive__sum'] neg = sums['negative__sum'] if pos is None: pos = 0 if neg is None: neg = 0 return pos + neg else: return 0 class Repute(models.Model): """The reputation histories for user""" user = models.ForeignKey(User) #todo: combine positive and negative to one value positive = models.SmallIntegerField(default=0) negative = models.SmallIntegerField(default=0) question = models.ForeignKey('Post', null=True, blank=True) reputed_at = models.DateTimeField(default=datetime.datetime.now) reputation_type = models.SmallIntegerField(choices=const.TYPE_REPUTATION) reputation = models.IntegerField(default=1) #comment that must be used if reputation_type == 10 #assigned_by_moderator - so that reason can be displayed #in that case Question field will be blank comment = models.CharField(max_length=128, null=True) objects = ReputeManager() def __unicode__(self): return u'[%s]\' reputation changed at %s' % (self.user.username, self.reputed_at) class Meta: app_label = 'askbot' db_table = u'repute' def get_explanation_snippet(self): """returns HTML snippet with a link to related question or a text description for a the reason of the reputation change in the implementation description is returned only for Repute.reputation_type == 10 - "assigned by the moderator" part of the purpose of this method is to hide this idiosyncracy """ if self.reputation_type == 10:#todo: hide magic number return _('Changed by moderator. Reason: %(reason)s') \ % {'reason':self.comment} else: delta = self.positive + self.negative#.negative is < 0 so we add! link_title_data = { 'points': abs(delta), 'username': self.user.username, 'question_title': self.question.thread.title } if delta > 0: link_title = _( '%(points)s points were added for %(username)s\'s ' 'contribution to question %(question_title)s' ) % link_title_data else: link_title = _( '%(points)s points were subtracted for %(username)s\'s ' 'contribution to question %(question_title)s' ) % link_title_data return '%(question_title)s' \ % { 'url': self.question.get_absolute_url(), 'question_title': escape(self.question.thread.title), 'link_title': escape(link_title) }