Commit 73e6e6f4 by Adam

Merge pull request #394 from edx/fix/foldit-leaderboard

Fix/foldit leaderboard
parents 032b02df a04539af
...@@ -91,15 +91,18 @@ class FolditModule(FolditFields, XModule): ...@@ -91,15 +91,18 @@ class FolditModule(FolditFields, XModule):
PuzzleComplete.completed_puzzles(self.system.anonymous_student_id), PuzzleComplete.completed_puzzles(self.system.anonymous_student_id),
key=lambda d: (d['set'], d['subset'])) key=lambda d: (d['set'], d['subset']))
def puzzle_leaders(self, n=10): def puzzle_leaders(self, n=10, courses=None):
""" """
Returns a list of n pairs (user, score) corresponding to the top Returns a list of n pairs (user, score) corresponding to the top
scores; the pairs are in descending order of score. scores; the pairs are in descending order of score.
""" """
from foldit.models import Score from foldit.models import Score
leaders = [(e['username'], e['score']) for e in Score.get_tops_n(10)] if courses is None:
leaders.sort(key=lambda x:-x[1]) courses = [self.location.course_id]
leaders = [(leader['username'], leader['score']) for leader in Score.get_tops_n(10, course_list=courses)]
leaders.sort(key=lambda x: -x[1])
return leaders return leaders
......
...@@ -6,6 +6,7 @@ from django.db import models ...@@ -6,6 +6,7 @@ from django.db import models
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Score(models.Model): class Score(models.Model):
""" """
This model stores the scores of different users on FoldIt problems. This model stores the scores of different users on FoldIt problems.
...@@ -35,9 +36,8 @@ class Score(models.Model): ...@@ -35,9 +36,8 @@ class Score(models.Model):
""" """
return (-score) * 10 + 8000 * sum_of return (-score) * 10 + 8000 * sum_of
@staticmethod @staticmethod
def get_tops_n(n, puzzles=['994559']): def get_tops_n(n, puzzles=['994559'], course_list=None):
""" """
Arguments: Arguments:
puzzles: a list of puzzle ids that we will use. If not specified, puzzles: a list of puzzle ids that we will use. If not specified,
...@@ -46,22 +46,34 @@ class Score(models.Model): ...@@ -46,22 +46,34 @@ class Score(models.Model):
Returns: Returns:
The top n sum of scores for puzzles in <puzzles>. Output is a list The top n sum of scores for puzzles in <puzzles>,
of disctionaries, sorted by display_score: 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:
[ {username: 'a_user', [ {username: 'a_user',
score: 12000} ...] score: 12000} ...]
""" """
if not(type(puzzles) == list):
if not isinstance(puzzles, list):
puzzles = [puzzles] puzzles = [puzzles]
scores = Score.objects \ if course_list is None:
.filter(puzzle_id__in=puzzles) \ scores = Score.objects \
.annotate(total_score=models.Sum('best_score')) \ .filter(puzzle_id__in=puzzles) \
.order_by('total_score')[:n] .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]
num = len(puzzles) num = len(puzzles)
return [{'username': s.user.username, return [
'score': Score.display_score(s.total_score, num)} {'username': score.user.username,
for s in scores] 'score': Score.display_score(score.total_score, num)}
for score in scores
]
class PuzzleComplete(models.Model): class PuzzleComplete(models.Model):
...@@ -94,7 +106,6 @@ class PuzzleComplete(models.Model): ...@@ -94,7 +106,6 @@ class PuzzleComplete(models.Model):
self.puzzle_set, self.puzzle_subset, self.puzzle_set, self.puzzle_subset,
self.created) self.created)
@staticmethod @staticmethod
def completed_puzzles(anonymous_user_id): def completed_puzzles(anonymous_user_id):
""" """
......
...@@ -2,14 +2,14 @@ import json ...@@ -2,14 +2,14 @@ import json
import logging import logging
from functools import partial from functools import partial
from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from foldit.views import foldit_ops, verify_code from foldit.views import foldit_ops, verify_code
from foldit.models import PuzzleComplete, Score from foldit.models import PuzzleComplete, Score
from student.models import UserProfile, unique_id_for_user from student.models import unique_id_for_user
from student.tests.factories import CourseEnrollmentFactory, UserFactory, UserProfileFactory
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pytz import UTC from pytz import UTC
...@@ -23,17 +23,25 @@ class FolditTestCase(TestCase): ...@@ -23,17 +23,25 @@ class FolditTestCase(TestCase):
self.factory = RequestFactory() self.factory = RequestFactory()
self.url = reverse('foldit_ops') self.url = reverse('foldit_ops')
pwd = 'abc' self.course_id = 'course/id/1'
self.user = User.objects.create_user('testuser', 'test@test.com', pwd) self.course_id2 = 'course/id/2'
self.user2 = User.objects.create_user('testuser2', 'test2@test.com', pwd)
self.unique_user_id = unique_id_for_user(self.user) self.user = UserFactory.create()
self.unique_user_id2 = unique_id_for_user(self.user2) self.user2 = UserFactory.create()
self.course_enrollment = CourseEnrollmentFactory.create(
user=self.user, course_id=self.course_id
)
self.course_enrollment2 = CourseEnrollmentFactory.create(
user=self.user2, course_id=self.course_id2
)
now = datetime.now(UTC) now = datetime.now(UTC)
self.tomorrow = now + timedelta(days=1) self.tomorrow = now + timedelta(days=1)
self.yesterday = now - timedelta(days=1) self.yesterday = now - timedelta(days=1)
UserProfile.objects.create(user=self.user) self.user.profile
UserProfile.objects.create(user=self.user2) self.user2.profile
def make_request(self, post_data, user=None): def make_request(self, post_data, user=None):
request = self.factory.post(self.url, post_data) request = self.factory.post(self.url, post_data)
...@@ -150,6 +158,38 @@ class FolditTestCase(TestCase): ...@@ -150,6 +158,38 @@ class FolditTestCase(TestCase):
delta=0.5 delta=0.5
) )
def test_SetPlayerPuzzleScores_multiplecourses(self):
puzzle_id = "1"
player1_score = 0.05
player2_score = 0.06
course_list_1 = [self.course_id]
course_list_2 = [self.course_id2]
response1 = self.make_puzzle_score_request(
puzzle_id, player1_score, self.user
)
course_1_top_10 = Score.get_tops_n(10, puzzle_id, course_list_1)
course_2_top_10 = Score.get_tops_n(10, puzzle_id, course_list_2)
total_top_10 = Score.get_tops_n(10, puzzle_id)
# player1 should now be in the top 10 of course 1 and not in course 2
self.assertEqual(len(course_1_top_10), 1)
self.assertEqual(len(course_2_top_10), 0)
self.assertEqual(len(total_top_10), 1)
response2 = self.make_puzzle_score_request(
puzzle_id, player2_score, self.user2
)
course_2_top_10 = Score.get_tops_n(10, puzzle_id, course_list_2)
total_top_10 = Score.get_tops_n(10, puzzle_id)
# player2 should now be in the top 10 of course 2 and not in course 1
self.assertEqual(len(course_1_top_10), 1)
self.assertEqual(len(course_2_top_10), 1)
self.assertEqual(len(total_top_10), 2)
def test_SetPlayerPuzzleScores_manyplayers(self): def test_SetPlayerPuzzleScores_manyplayers(self):
""" """
Check that when we send scores from multiple users, the correct order Check that when we send scores from multiple users, the correct order
...@@ -306,7 +346,7 @@ class FolditTestCase(TestCase): ...@@ -306,7 +346,7 @@ class FolditTestCase(TestCase):
self.set_puzzle_complete_response([13, 14, 15, 53524])) self.set_puzzle_complete_response([13, 14, 15, 53524]))
is_complete = partial( is_complete = partial(
PuzzleComplete.is_level_complete, self.unique_user_id) PuzzleComplete.is_level_complete, unique_id_for_user(self.user))
self.assertTrue(is_complete(1, 1)) self.assertTrue(is_complete(1, 1))
self.assertTrue(is_complete(1, 3)) self.assertTrue(is_complete(1, 3))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment