models.py 5.01 KB
Newer Older
Victor Shnayder committed
1 2 3 4 5 6 7 8
import logging

from django.contrib.auth.models import User
from django.db import models


log = logging.getLogger(__name__)

9

Victor Shnayder committed
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
class Score(models.Model):
    """
    This model stores the scores of different users on FoldIt problems.
    """
    user = models.ForeignKey(User, db_index=True,
                             related_name='foldit_scores')

    # The XModule that wants to access this doesn't have access to the real
    # userid.  Save the anonymized version so we can look up by that.
    unique_user_id = models.CharField(max_length=50, db_index=True)
    puzzle_id = models.IntegerField()
    best_score = models.FloatField(db_index=True)
    current_score = models.FloatField(db_index=True)
    score_version = models.IntegerField()
    created = models.DateTimeField(auto_now_add=True)

26
    @staticmethod
27
    def display_score(score, sum_of=1):
28 29
        """
        Argument:
30 31 32
            score (float), as stored in the DB (i.e., "rosetta score")
            sum_of (int): if this score is the sum of scores of individual
               problems, how many elements are in that sum
33 34 35 36

        Returns:
            score (float), as displayed to the user in the game and in the leaderboard
        """
37
        return (-score) * 10 + 8000 * sum_of
38

39
    @staticmethod
40
    def get_tops_n(n, puzzles=['994559'], course_list=None):
41 42 43 44 45 46 47 48
        """
        Arguments:
            puzzles: a list of puzzle ids that we will use. If not specified,
            defaults to puzzle used in 7012x.
            n (int): number of top scores to return


        Returns:
49 50 51 52
            The top n sum of scores for puzzles in <puzzles>,
            filtered by course. If no courses is specified we default
            the pool of students to all courses. Output is a list
            of dictionaries, sorted by display_score:
53 54 55
                [ {username: 'a_user',
                   score: 12000} ...]
        """
56 57

        if not isinstance(puzzles, list):
Julian Arni committed
58
            puzzles = [puzzles]
59 60 61 62 63 64 65 66 67 68 69
        if course_list is None:
            scores = Score.objects \
                .filter(puzzle_id__in=puzzles) \
                .annotate(total_score=models.Sum('best_score')) \
                .order_by('total_score')[:n]
        else:
            scores = Score.objects \
                .filter(puzzle_id__in=puzzles) \
                .filter(user__courseenrollment__course_id__in=course_list) \
                .annotate(total_score=models.Sum('best_score')) \
                .order_by('total_score')[:n]
70 71
        num = len(puzzles)

72 73 74 75 76
        return [
            {'username': score.user.username,
             'score': Score.display_score(score.total_score, num)}
            for score in scores
        ]
77

Victor Shnayder committed
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

class PuzzleComplete(models.Model):
    """
    This keeps track of the sets of puzzles completed by each user.

    e.g. PuzzleID 1234, set 1, subset 3.  (Sets and subsets correspond to levels
    in the intro puzzles)
    """
    class Meta:
        # there should only be one puzzle complete entry for any particular
        # puzzle for any user
        unique_together = ('user', 'puzzle_id', 'puzzle_set', 'puzzle_subset')
        ordering = ['puzzle_id']

    user = models.ForeignKey(User, db_index=True,
                             related_name='foldit_puzzles_complete')

    # The XModule that wants to access this doesn't have access to the real
    # userid.  Save the anonymized version so we can look up by that.
    unique_user_id = models.CharField(max_length=50, db_index=True)
    puzzle_id = models.IntegerField()
    puzzle_set = models.IntegerField(db_index=True)
    puzzle_subset = models.IntegerField(db_index=True)
    created = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return "PuzzleComplete({0}, id={1}, set={2}, subset={3}, created={4})".format(
            self.user.username, self.puzzle_id,
            self.puzzle_set, self.puzzle_subset,
            self.created)

    @staticmethod
    def completed_puzzles(anonymous_user_id):
        """
        Return a list of puzzles that this user has completed, as an array of
        dicts:

        [ {'set': int,
           'subset': int,
           'created': datetime} ]
        """
        complete = PuzzleComplete.objects.filter(unique_user_id=anonymous_user_id)
        return [{'set': c.puzzle_set,
                 'subset': c.puzzle_subset,
                 'created': c.created} for c in complete]


    @staticmethod
    def is_level_complete(anonymous_user_id, level, sub_level, due=None):
        """
        Return True if this user completed level--sub_level by due.

        Users see levels as e.g. 4-5.

        Args:
            level: int
            sub_level: int
            due (optional): If specified, a datetime.  Ignored if None.
        """
        complete = PuzzleComplete.objects.filter(unique_user_id=anonymous_user_id,
                                                 puzzle_set=level,
                                                 puzzle_subset=sub_level)
        if due is not None:
            complete = complete.filter(created__lte=due)

        return complete.exists()