From 84a96e40c4abf3ae70f6a3202800ce403a53d166 Mon Sep 17 00:00:00 2001 From: Sarina Canelake <sarina@edx.org> Date: Sun, 6 Dec 2015 11:38:05 -0500 Subject: [PATCH] Remove 'Fold It' XModule --- common/lib/xmodule/setup.py | 1 - .../xmodule/css/foldit/leaderboard.scss | 20 - common/lib/xmodule/xmodule/foldit_module.py | 206 --------- common/test/db_cache/bok_choy_schema.sql | 39 -- lms/djangoapps/courseware/grades.py | 5 +- lms/djangoapps/foldit/__init__.py | 0 .../foldit/migrations/0001_initial.py | 47 -- lms/djangoapps/foldit/migrations/__init__.py | 0 lms/djangoapps/foldit/models.py | 142 ------ lms/djangoapps/foldit/tests.py | 403 ------------------ lms/djangoapps/foldit/views.py | 175 -------- lms/envs/common.py | 3 - .../sass/course/courseware/_courseware.scss | 14 - lms/templates/foldit.html | 12 - lms/templates/folditbasic.html | 33 -- lms/templates/folditchallenge.html | 18 - lms/urls.py | 6 - 17 files changed, 2 insertions(+), 1122 deletions(-) delete mode 100644 common/lib/xmodule/xmodule/css/foldit/leaderboard.scss delete mode 100644 common/lib/xmodule/xmodule/foldit_module.py delete mode 100644 lms/djangoapps/foldit/__init__.py delete mode 100644 lms/djangoapps/foldit/migrations/0001_initial.py delete mode 100644 lms/djangoapps/foldit/migrations/__init__.py delete mode 100644 lms/djangoapps/foldit/models.py delete mode 100644 lms/djangoapps/foldit/tests.py delete mode 100644 lms/djangoapps/foldit/views.py delete mode 100644 lms/templates/foldit.html delete mode 100644 lms/templates/folditbasic.html delete mode 100644 lms/templates/folditchallenge.html diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index e1e50daf981..5b6b0b58f5a 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -36,7 +36,6 @@ XMODULES = [ "textannotation = xmodule.textannotation_module:TextAnnotationDescriptor", "videoannotation = xmodule.videoannotation_module:VideoAnnotationDescriptor", "imageannotation = xmodule.imageannotation_module:ImageAnnotationDescriptor", - "foldit = xmodule.foldit_module:FolditDescriptor", "word_cloud = xmodule.word_cloud_module:WordCloudDescriptor", "hidden = xmodule.hidden_module:HiddenDescriptor", "raw = xmodule.raw_module:RawDescriptor", diff --git a/common/lib/xmodule/xmodule/css/foldit/leaderboard.scss b/common/lib/xmodule/xmodule/css/foldit/leaderboard.scss deleted file mode 100644 index 3173ffbcdcc..00000000000 --- a/common/lib/xmodule/xmodule/css/foldit/leaderboard.scss +++ /dev/null @@ -1,20 +0,0 @@ -$leaderboard: #F4F4F4; - -section.foldit { - div.folditchallenge { - table { - border: 1px solid lighten($leaderboard, 10%); - border-collapse: collapse; - margin-top: $baseline; - } - th { - background: $leaderboard; - color: darken($leaderboard, 25%); - } - td { - background: lighten($leaderboard, 3%); - border-bottom: 1px solid $white; - padding: 8px; - } - } -} diff --git a/common/lib/xmodule/xmodule/foldit_module.py b/common/lib/xmodule/xmodule/foldit_module.py deleted file mode 100644 index 67f3c93791e..00000000000 --- a/common/lib/xmodule/xmodule/foldit_module.py +++ /dev/null @@ -1,206 +0,0 @@ -import logging -from lxml import etree - -from pkg_resources import resource_string - -from xmodule.editing_module import EditingDescriptor -from xmodule.x_module import XModule -from xmodule.xml_module import XmlDescriptor -from xblock.fields import Scope, Integer, String -from .fields import Date - - -log = logging.getLogger(__name__) - - -class FolditFields(object): - # default to what Spring_7012x uses - required_level_half_credit = Integer(default=3, scope=Scope.settings) - required_sublevel_half_credit = Integer(default=5, scope=Scope.settings) - required_level = Integer(default=4, scope=Scope.settings) - required_sublevel = Integer(default=5, scope=Scope.settings) - due = Date(help="Date that this problem is due by", scope=Scope.settings) - - show_basic_score = String(scope=Scope.settings, default='false') - show_leaderboard = String(scope=Scope.settings, default='false') - - -class FolditModule(FolditFields, XModule): - - css = {'scss': [resource_string(__name__, 'css/foldit/leaderboard.scss')]} - - def __init__(self, *args, **kwargs): - """ - Example: - <foldit show_basic_score="true" - required_level="4" - required_sublevel="3" - required_level_half_credit="2" - required_sublevel_half_credit="3" - show_leaderboard="false"/> - """ - super(FolditModule, self).__init__(*args, **kwargs) - self.due_time = self.due - - def is_complete(self): - """ - Did the user get to the required level before the due date? - """ - # We normally don't want django dependencies in xmodule. foldit is - # special. Import this late to avoid errors with things not yet being - # initialized. - from foldit.models import PuzzleComplete - - complete = PuzzleComplete.is_level_complete( - self.system.anonymous_student_id, - self.required_level, - self.required_sublevel, - self.due_time) - return complete - - def is_half_complete(self): - """ - Did the user reach the required level for half credit? - - Ideally this would be more flexible than just 0, 0.5, or 1 credit. On - the other hand, the xml attributes for specifying more specific - cut-offs and partial grades can get more confusing. - """ - from foldit.models import PuzzleComplete - complete = PuzzleComplete.is_level_complete( - self.system.anonymous_student_id, - self.required_level_half_credit, - self.required_sublevel_half_credit, - self.due_time) - return complete - - def completed_puzzles(self): - """ - Return a list of puzzles that this user has completed, as an array of - dicts: - - [ {'set': int, - 'subset': int, - 'created': datetime} ] - - The list is sorted by set, then subset - """ - from foldit.models import PuzzleComplete - - return sorted( - PuzzleComplete.completed_puzzles(self.system.anonymous_student_id), - key=lambda d: (d['set'], d['subset'])) - - def puzzle_leaders(self, n=10, courses=None): - """ - Returns a list of n pairs (user, score) corresponding to the top - scores; the pairs are in descending order of score. - """ - from foldit.models import Score - - if courses is None: - courses = [self.location.course_key] - - 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 - - def get_html(self): - """ - Render the html for the module. - """ - goal_level = '{0}-{1}'.format( - self.required_level, - self.required_sublevel) - - showbasic = (self.show_basic_score.lower() == "true") - showleader = (self.show_leaderboard.lower() == "true") - - context = { - 'due': self.due, - 'success': self.is_complete(), - 'goal_level': goal_level, - 'completed': self.completed_puzzles(), - 'top_scores': self.puzzle_leaders(), - 'show_basic': showbasic, - 'show_leader': showleader, - 'folditbasic': self.get_basicpuzzles_html(), - 'folditchallenge': self.get_challenge_html() - } - - return self.system.render_template('foldit.html', context) - - def get_basicpuzzles_html(self): - """ - Render html for the basic puzzle section. - """ - goal_level = '{0}-{1}'.format( - self.required_level, - self.required_sublevel) - - context = { - 'due': self.due, - 'success': self.is_complete(), - 'goal_level': goal_level, - 'completed': self.completed_puzzles(), - } - return self.system.render_template('folditbasic.html', context) - - def get_challenge_html(self): - """ - Render html for challenge (i.e., the leaderboard) - """ - - context = { - 'top_scores': self.puzzle_leaders()} - - return self.system.render_template('folditchallenge.html', context) - - def get_score(self): - """ - 0 if required_level_half_credit - required_sublevel_half_credit not - reached. - 0.5 if required_level_half_credit and required_sublevel_half_credit - reached. - 1 if requred_level and required_sublevel reached. - """ - if self.is_complete(): - score = 1 - elif self.is_half_complete(): - score = 0.5 - else: - score = 0 - return {'score': score, - 'total': self.max_score()} - - def max_score(self): - return 1 - - -class FolditDescriptor(FolditFields, XmlDescriptor, EditingDescriptor): - """ - Module for adding Foldit problems to courses - """ - mako_template = "widgets/html-edit.html" - module_class = FolditModule - filename_extension = "xml" - - has_score = True - - show_in_read_only_mode = True - - js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]} - js_module_name = "HTMLEditingDescriptor" - - # The grade changes without any student interaction with the edx website, - # so always need to actually check. - always_recalculate_grades = True - - @classmethod - def definition_from_xml(cls, xml_object, system): - return {}, [] - - def definition_to_xml(self, resource_fs): - xml_object = etree.Element('foldit') - return xml_object diff --git a/common/test/db_cache/bok_choy_schema.sql b/common/test/db_cache/bok_choy_schema.sql index 9856d86aa3e..a580794e373 100644 --- a/common/test/db_cache/bok_choy_schema.sql +++ b/common/test/db_cache/bok_choy_schema.sql @@ -1810,45 +1810,6 @@ CREATE TABLE `external_auth_externalauthmap` ( CONSTRAINT `external_auth_externala_user_id_644e7779f2d52b9a_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -DROP TABLE IF EXISTS `foldit_puzzlecomplete`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `foldit_puzzlecomplete` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `unique_user_id` varchar(50) NOT NULL, - `puzzle_id` int(11) NOT NULL, - `puzzle_set` int(11) NOT NULL, - `puzzle_subset` int(11) NOT NULL, - `created` datetime(6) NOT NULL, - `user_id` int(11) NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `foldit_puzzlecomplete_user_id_4c63656af6674331_uniq` (`user_id`,`puzzle_id`,`puzzle_set`,`puzzle_subset`), - KEY `foldit_puzzlecomplete_ff2b2d15` (`unique_user_id`), - KEY `foldit_puzzlecomplete_56c088b4` (`puzzle_set`), - KEY `foldit_puzzlecomplete_2dc27ffb` (`puzzle_subset`), - CONSTRAINT `foldit_puzzlecomplete_user_id_cd0294fb3a392_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; -DROP TABLE IF EXISTS `foldit_score`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `foldit_score` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `unique_user_id` varchar(50) NOT NULL, - `puzzle_id` int(11) NOT NULL, - `best_score` double NOT NULL, - `current_score` double NOT NULL, - `score_version` int(11) NOT NULL, - `created` datetime(6) NOT NULL, - `user_id` int(11) NOT NULL, - PRIMARY KEY (`id`), - KEY `foldit_score_user_id_6ac502fe1f6861b2_fk_auth_user_id` (`user_id`), - KEY `foldit_score_ff2b2d15` (`unique_user_id`), - KEY `foldit_score_44726e86` (`best_score`), - KEY `foldit_score_32d6f808` (`current_score`), - CONSTRAINT `foldit_score_user_id_6ac502fe1f6861b2_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; DROP TABLE IF EXISTS `instructor_task_instructortask`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; diff --git a/lms/djangoapps/courseware/grades.py b/lms/djangoapps/courseware/grades.py index 06a5fff5c9f..1becdeb04b0 100644 --- a/lms/djangoapps/courseware/grades.py +++ b/lms/djangoapps/courseware/grades.py @@ -378,8 +378,7 @@ def _grade(student, request, course, keep_raw_scores, field_data_cache, scores_c with outer_atomic(): # some problems have state that is updated independently of interaction - # with the LMS, so they need to always be scored. (E.g. foldit., - # combinedopenended) + # with the LMS, so they need to always be scored. (E.g. combinedopenended ORA1) # TODO This block is causing extra savepoints to be fired that are empty because no queries are executed # during the loop. When refactoring this code please keep this outer_atomic call in mind and ensure we # are not making unnecessary database queries. @@ -699,7 +698,7 @@ def get_score(user, problem_descriptor, module_creator, scores_client, submissio return submissions_scores_cache[location_url] # some problems have state that is updated independently of interaction - # with the LMS, so they need to always be scored. (E.g. foldit.) + # with the LMS, so they need to always be scored. (E.g. combinedopenended ORA1.) if problem_descriptor.always_recalculate_grades: problem = module_creator(problem_descriptor) if problem is None: diff --git a/lms/djangoapps/foldit/__init__.py b/lms/djangoapps/foldit/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/lms/djangoapps/foldit/migrations/0001_initial.py b/lms/djangoapps/foldit/migrations/0001_initial.py deleted file mode 100644 index e73af4e0395..00000000000 --- a/lms/djangoapps/foldit/migrations/0001_initial.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models -from django.conf import settings - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='PuzzleComplete', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('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)), - ('user', models.ForeignKey(related_name='foldit_puzzles_complete', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'ordering': ['puzzle_id'], - }, - ), - migrations.CreateModel( - name='Score', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('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)), - ('user', models.ForeignKey(related_name='foldit_scores', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.AlterUniqueTogether( - name='puzzlecomplete', - unique_together=set([('user', 'puzzle_id', 'puzzle_set', 'puzzle_subset')]), - ), - ] diff --git a/lms/djangoapps/foldit/migrations/__init__.py b/lms/djangoapps/foldit/migrations/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/lms/djangoapps/foldit/models.py b/lms/djangoapps/foldit/models.py deleted file mode 100644 index 763f3951c4a..00000000000 --- a/lms/djangoapps/foldit/models.py +++ /dev/null @@ -1,142 +0,0 @@ -import logging - -from django.contrib.auth.models import User -from django.db import models - - -log = logging.getLogger(__name__) - - -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) - - @staticmethod - def display_score(score, sum_of=1): - """ - Argument: - 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 - - Returns: - score (float), as displayed to the user in the game and in the leaderboard - """ - return (-score) * 10 + 8000 * sum_of - - @staticmethod - def get_tops_n(n, puzzles=['994559'], course_list=None): - """ - 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: - 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: - [ {username: 'a_user', - score: 12000} ...] - """ - - if not isinstance(puzzles, list): - puzzles = [puzzles] - 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] - num = len(puzzles) - - return [ - {'username': score.user.username, - 'score': Score.display_score(score.total_score, num)} - for score in scores - ] - - -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(object): - # 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() diff --git a/lms/djangoapps/foldit/tests.py b/lms/djangoapps/foldit/tests.py deleted file mode 100644 index 2ff6da98f5c..00000000000 --- a/lms/djangoapps/foldit/tests.py +++ /dev/null @@ -1,403 +0,0 @@ -"""Tests for the FoldIt module""" -import json -import logging -from functools import partial - -from django.test import TestCase -from django.test.client import RequestFactory -from django.core.urlresolvers import reverse - -from foldit.views import foldit_ops, verify_code -from foldit.models import PuzzleComplete, Score -from student.models import unique_id_for_user, CourseEnrollment -from student.tests.factories import UserFactory - -from datetime import datetime, timedelta -from pytz import UTC -from opaque_keys.edx.locations import SlashSeparatedCourseKey - -log = logging.getLogger(__name__) - - -class FolditTestCase(TestCase): - """Tests for various responses of the FoldIt module""" - def setUp(self): - super(FolditTestCase, self).setUp() - - self.factory = RequestFactory() - self.url = reverse('foldit_ops') - - self.course_id = SlashSeparatedCourseKey('course', 'id', '1') - self.course_id2 = SlashSeparatedCourseKey('course', 'id', '2') - - self.user = UserFactory.create() - self.user2 = UserFactory.create() - - CourseEnrollment.enroll(self.user, self.course_id) - CourseEnrollment.enroll(self.user2, self.course_id2) - - now = datetime.now(UTC) - self.tomorrow = now + timedelta(days=1) - self.yesterday = now - timedelta(days=1) - - def make_request(self, post_data, user=None): - """Makes a request to foldit_ops with the given post data and user (if specified)""" - request = self.factory.post(self.url, post_data) - request.user = self.user if not user else user - return request - - def make_puzzle_score_request(self, puzzle_ids, best_scores, user=None): - """ - Given lists of puzzle_ids and best_scores (must have same length), make a - SetPlayerPuzzleScores request and return the response. - """ - if not isinstance(best_scores, list): - best_scores = [best_scores] - if not isinstance(puzzle_ids, list): - puzzle_ids = [puzzle_ids] - user = self.user if not user else user - - def score_dict(puzzle_id, best_score): - """Returns a valid json-parsable score dict""" - return {"PuzzleID": puzzle_id, - "ScoreType": "score", - "BestScore": best_score, - # current scores don't actually matter - "CurrentScore": best_score + 0.01, - "ScoreVersion": 23} - scores = [score_dict(pid, bs) for pid, bs in zip(puzzle_ids, best_scores)] - scores_str = json.dumps(scores) - - verify = {"Verify": verify_code(user.email, scores_str), - "VerifyMethod": "FoldItVerify"} - data = {'SetPlayerPuzzleScoresVerify': json.dumps(verify), - 'SetPlayerPuzzleScores': scores_str} - - request = self.make_request(data, user) - - response = foldit_ops(request) - self.assertEqual(response.status_code, 200) - return response - - def test_SetPlayerPuzzleScores(self): # pylint: disable=invalid-name - - puzzle_id = 994391 - best_score = 0.078034 - response = self.make_puzzle_score_request(puzzle_id, [best_score]) - - self.assertEqual(response.content, json.dumps( - [{"OperationID": "SetPlayerPuzzleScores", - "Value": [{ - "PuzzleID": puzzle_id, - "Status": "Success"}]}])) - - # There should now be a score in the db. - top_10 = Score.get_tops_n(10, puzzle_id) - self.assertEqual(len(top_10), 1) - self.assertEqual(top_10[0]['score'], Score.display_score(best_score)) - - def test_SetPlayerPuzzleScores_many(self): # pylint: disable=invalid-name - - response = self.make_puzzle_score_request([1, 2], [0.078034, 0.080000]) - - self.assertEqual(response.content, json.dumps( - [{ - "OperationID": "SetPlayerPuzzleScores", - "Value": [ - { - "PuzzleID": 1, - "Status": "Success" - }, { - "PuzzleID": 2, - "Status": "Success" - } - ] - }] - )) - - def test_SetPlayerPuzzleScores_multiple(self): # pylint: disable=invalid-name - """ - Check that multiple posts with the same id are handled properly - (keep latest for each user, have multiple users work properly) - """ - orig_score = 0.07 - puzzle_id = '1' - self.make_puzzle_score_request([puzzle_id], [orig_score]) - - # There should now be a score in the db. - top_10 = Score.get_tops_n(10, puzzle_id) - self.assertEqual(len(top_10), 1) - self.assertEqual(top_10[0]['score'], Score.display_score(orig_score)) - - # Reporting a better score should overwrite - better_score = 0.06 - self.make_puzzle_score_request([1], [better_score]) - - top_10 = Score.get_tops_n(10, puzzle_id) - self.assertEqual(len(top_10), 1) - - # Floats always get in the way, so do almostequal - self.assertAlmostEqual( - top_10[0]['score'], - Score.display_score(better_score), - delta=0.5 - ) - - # reporting a worse score shouldn't - worse_score = 0.065 - self.make_puzzle_score_request([1], [worse_score]) - - top_10 = Score.get_tops_n(10, puzzle_id) - self.assertEqual(len(top_10), 1) - # should still be the better score - self.assertAlmostEqual( - top_10[0]['score'], - Score.display_score(better_score), - delta=0.5 - ) - - def test_SetPlayerPuzzleScores_multiple_courses(self): # pylint: disable=invalid-name - puzzle_id = "1" - - player1_score = 0.05 - player2_score = 0.06 - - course_list_1 = [self.course_id] - course_list_2 = [self.course_id2] - - 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) - - 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_many_players(self): # pylint: disable=invalid-name - """ - Check that when we send scores from multiple users, the correct order - of scores is displayed. Note that, before being processed by - display_score, lower scores are better. - """ - puzzle_id = ['1'] - player1_score = 0.08 - player2_score = 0.02 - self.make_puzzle_score_request(puzzle_id, player1_score, self.user) - - # There should now be a score in the db. - top_10 = Score.get_tops_n(10, puzzle_id) - self.assertEqual(len(top_10), 1) - self.assertEqual(top_10[0]['score'], Score.display_score(player1_score)) - - self.make_puzzle_score_request(puzzle_id, player2_score, self.user2) - - # There should now be two scores in the db - top_10 = Score.get_tops_n(10, puzzle_id) - self.assertEqual(len(top_10), 2) - - # Top score should be player2_score. Second should be player1_score - self.assertAlmostEqual( - top_10[0]['score'], - Score.display_score(player2_score), - delta=0.5 - ) - self.assertAlmostEqual( - top_10[1]['score'], - Score.display_score(player1_score), - delta=0.5 - ) - - # Top score user should be self.user2.username - self.assertEqual(top_10[0]['username'], self.user2.username) - - def test_SetPlayerPuzzleScores_error(self): # pylint: disable=invalid-name - - scores = [{ - "PuzzleID": 994391, - "ScoreType": "score", - "BestScore": 0.078034, - "CurrentScore": 0.080035, - "ScoreVersion": 23 - }] - validation_str = json.dumps(scores) - - verify = { - "Verify": verify_code(self.user.email, validation_str), - "VerifyMethod": "FoldItVerify" - } - - # change the real string -- should get an error - scores[0]['ScoreVersion'] = 22 - scores_str = json.dumps(scores) - - data = { - 'SetPlayerPuzzleScoresVerify': json.dumps(verify), - 'SetPlayerPuzzleScores': scores_str - } - - request = self.make_request(data) - - response = foldit_ops(request) - self.assertEqual(response.status_code, 200) - - self.assertEqual(response.content, - json.dumps([{ - "OperationID": "SetPlayerPuzzleScores", - "Success": "false", - "ErrorString": "Verification failed", - "ErrorCode": "VerifyFailed"}])) - - def make_puzzles_complete_request(self, puzzles): - """ - Make a puzzles complete request, given an array of - puzzles. E.g. - - [ {"PuzzleID": 13, "Set": 1, "SubSet": 2}, - {"PuzzleID": 53524, "Set": 1, "SubSet": 1} ] - """ - puzzles_str = json.dumps(puzzles) - - verify = { - "Verify": verify_code(self.user.email, puzzles_str), - "VerifyMethod": "FoldItVerify" - } - - data = { - 'SetPuzzlesCompleteVerify': json.dumps(verify), - 'SetPuzzlesComplete': puzzles_str - } - - request = self.make_request(data) - - response = foldit_ops(request) - self.assertEqual(response.status_code, 200) - return response - - @staticmethod - def set_puzzle_complete_response(values): - """Returns a json response of a Puzzle Complete message""" - return json.dumps([{"OperationID": "SetPuzzlesComplete", - "Value": values}]) - - def test_SetPlayerPuzzlesComplete(self): # pylint: disable=invalid-name - - puzzles = [ - {"PuzzleID": 13, "Set": 1, "SubSet": 2}, - {"PuzzleID": 53524, "Set": 1, "SubSet": 1} - ] - - response = self.make_puzzles_complete_request(puzzles) - - self.assertEqual(response.content, - self.set_puzzle_complete_response([13, 53524])) - - def test_SetPlayerPuzzlesComplete_multiple(self): # pylint: disable=invalid-name - """Check that state is stored properly""" - - puzzles = [ - {"PuzzleID": 13, "Set": 1, "SubSet": 2}, - {"PuzzleID": 53524, "Set": 1, "SubSet": 1} - ] - - response = self.make_puzzles_complete_request(puzzles) - - self.assertEqual(response.content, - self.set_puzzle_complete_response([13, 53524])) - - puzzles = [ - {"PuzzleID": 14, "Set": 1, "SubSet": 3}, - {"PuzzleID": 15, "Set": 1, "SubSet": 1} - ] - - response = self.make_puzzles_complete_request(puzzles) - - self.assertEqual( - response.content, - self.set_puzzle_complete_response([13, 14, 15, 53524]) - ) - - def test_SetPlayerPuzzlesComplete_level_complete(self): # pylint: disable=invalid-name - """Check that the level complete function works""" - - puzzles = [ - {"PuzzleID": 13, "Set": 1, "SubSet": 2}, - {"PuzzleID": 53524, "Set": 1, "SubSet": 1} - ] - - response = self.make_puzzles_complete_request(puzzles) - - self.assertEqual(response.content, - self.set_puzzle_complete_response([13, 53524])) - - puzzles = [ - {"PuzzleID": 14, "Set": 1, "SubSet": 3}, - {"PuzzleID": 15, "Set": 1, "SubSet": 1} - ] - - response = self.make_puzzles_complete_request(puzzles) - - self.assertEqual(response.content, - self.set_puzzle_complete_response([13, 14, 15, 53524])) - - is_complete = partial( - PuzzleComplete.is_level_complete, unique_id_for_user(self.user)) - - self.assertTrue(is_complete(1, 1)) - self.assertTrue(is_complete(1, 3)) - self.assertTrue(is_complete(1, 2)) - self.assertFalse(is_complete(4, 5)) - - puzzles = [{"PuzzleID": 74, "Set": 4, "SubSet": 5}] - - response = self.make_puzzles_complete_request(puzzles) - - self.assertTrue(is_complete(4, 5)) - - # Now check due dates - - self.assertTrue(is_complete(1, 1, due=self.tomorrow)) - self.assertFalse(is_complete(1, 1, due=self.yesterday)) - - def test_SetPlayerPuzzlesComplete_error(self): # pylint: disable=invalid-name - - puzzles = [ - {"PuzzleID": 13, "Set": 1, "SubSet": 2}, - {"PuzzleID": 53524, "Set": 1, "SubSet": 1} - ] - - puzzles_str = json.dumps(puzzles) - - verify = { - "Verify": verify_code(self.user.email, puzzles_str + "x"), - "VerifyMethod": "FoldItVerify" - } - - data = { - 'SetPuzzlesCompleteVerify': json.dumps(verify), - 'SetPuzzlesComplete': puzzles_str - } - - request = self.make_request(data) - - response = foldit_ops(request) - self.assertEqual(response.status_code, 200) - - self.assertEqual(response.content, - json.dumps([{ - "OperationID": "SetPuzzlesComplete", - "Success": "false", - "ErrorString": "Verification failed", - "ErrorCode": "VerifyFailed"}])) diff --git a/lms/djangoapps/foldit/views.py b/lms/djangoapps/foldit/views.py deleted file mode 100644 index 91423bbf887..00000000000 --- a/lms/djangoapps/foldit/views.py +++ /dev/null @@ -1,175 +0,0 @@ -import hashlib -import json -import logging - -from django.contrib.auth.decorators import login_required -from django.http import HttpResponse -from django.views.decorators.http import require_POST -from django.views.decorators.csrf import csrf_exempt - -from foldit.models import Score, PuzzleComplete -from student.models import unique_id_for_user - -import re - -log = logging.getLogger(__name__) - - -@login_required -@csrf_exempt -@require_POST -def foldit_ops(request): - """ - Endpoint view for foldit operations. - """ - responses = [] - if "SetPlayerPuzzleScores" in request.POST: - puzzle_scores_json = request.POST.get("SetPlayerPuzzleScores") - pz_verify_json = request.POST.get("SetPlayerPuzzleScoresVerify") - log.debug("SetPlayerPuzzleScores message: puzzle scores: %r", - puzzle_scores_json) - - puzzle_score_verify = json.loads(pz_verify_json) - if not verifies_ok(request.user.email, - puzzle_scores_json, puzzle_score_verify): - responses.append({"OperationID": "SetPlayerPuzzleScores", - "Success": "false", - "ErrorString": "Verification failed", - "ErrorCode": "VerifyFailed"}) - log.warning( - "Verification of SetPlayerPuzzleScores failed:" - "user %s, scores json %r, verify %r", - request.user, - puzzle_scores_json, - pz_verify_json - ) - else: - # This is needed because we are not getting valid json - the - # value of ScoreType is an unquoted string. Right now regexes are - # quoting the string, but ideally the json itself would be fixed. - # To allow for fixes without breaking this, the regex should only - # match unquoted strings, - a = re.compile(r':([a-zA-Z]*),') - puzzle_scores_json = re.sub(a, r':"\g<1>",', puzzle_scores_json) - puzzle_scores = json.loads(puzzle_scores_json) - responses.append(save_scores(request.user, puzzle_scores)) - - if "SetPuzzlesComplete" in request.POST: - puzzles_complete_json = request.POST.get("SetPuzzlesComplete") - pc_verify_json = request.POST.get("SetPuzzlesCompleteVerify") - - log.debug("SetPuzzlesComplete message: %r", - puzzles_complete_json) - - puzzles_complete_verify = json.loads(pc_verify_json) - - if not verifies_ok(request.user.email, - puzzles_complete_json, puzzles_complete_verify): - responses.append({"OperationID": "SetPuzzlesComplete", - "Success": "false", - "ErrorString": "Verification failed", - "ErrorCode": "VerifyFailed"}) - log.warning( - "Verification of SetPuzzlesComplete failed:" - " user %s, puzzles json %r, verify %r", - request.user, - puzzles_complete_json, - pc_verify_json - ) - else: - puzzles_complete = json.loads(puzzles_complete_json) - responses.append(save_complete(request.user, puzzles_complete)) - - return HttpResponse(json.dumps(responses)) - - -def verify_code(email, val): - """ - Given the email and passed in value (str), return the expected - verification code. - """ - # TODO: is this the right string? - verification_string = email.lower() + '|' + val - return hashlib.md5(verification_string).hexdigest() - - -def verifies_ok(email, val, verification): - """ - Check that the hash_str matches the expected hash of val. - - Returns True if verification ok, False otherwise - """ - if verification.get("VerifyMethod") != "FoldItVerify": - log.debug("VerificationMethod in %r isn't FoldItVerify", verification) - return False - hash_str = verification.get("Verify") - - return verify_code(email, val) == hash_str - - -def save_scores(user, puzzle_scores): - score_responses = [] - for score in puzzle_scores: - log.debug("score: %s", score) - # expected keys ScoreType, PuzzleID (int), - # BestScore (energy), CurrentScore (Energy), ScoreVersion (int) - - puzzle_id = score['PuzzleID'] - best_score = score['BestScore'] - current_score = score['CurrentScore'] - score_version = score['ScoreVersion'] - - # SetPlayerPuzzleScoreResponse object - # Score entries are unique on user/unique_user_id/puzzle_id/score_version - try: - obj = Score.objects.get( - user=user, - unique_user_id=unique_id_for_user(user), - puzzle_id=puzzle_id, - score_version=score_version) - obj.current_score = current_score - obj.best_score = best_score - - except Score.DoesNotExist: - obj = Score( - user=user, - unique_user_id=unique_id_for_user(user), - puzzle_id=puzzle_id, - current_score=current_score, - best_score=best_score, - score_version=score_version) - obj.save() - - score_responses.append({'PuzzleID': puzzle_id, - 'Status': 'Success'}) - - return {"OperationID": "SetPlayerPuzzleScores", "Value": score_responses} - - -def save_complete(user, puzzles_complete): - """ - Returned list of PuzzleIDs should be in sorted order (I don't think client - cares, but tests do) - """ - for complete in puzzles_complete: - log.debug("Puzzle complete: %s", complete) - puzzle_id = complete['PuzzleID'] - puzzle_set = complete['Set'] - puzzle_subset = complete['SubSet'] - - # create if not there - PuzzleComplete.objects.get_or_create( - user=user, - unique_user_id=unique_id_for_user(user), - puzzle_id=puzzle_id, - puzzle_set=puzzle_set, - puzzle_subset=puzzle_subset) - - # List of all puzzle ids of intro-level puzzles completed ever, including on this - # request - # TODO: this is just in this request... - - complete_responses = list(pc.puzzle_id - for pc in PuzzleComplete.objects.filter(user=user)) - - return {"OperationID": "SetPuzzlesComplete", "Value": complete_responses} diff --git a/lms/envs/common.py b/lms/envs/common.py index 66f47211f71..c558c53d459 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1872,9 +1872,6 @@ INSTALLED_APPS = ( #'wiki.plugins.notifications', 'course_wiki.plugins.markdownedx', - # Foldit integration - 'foldit', - # For testing 'django.contrib.admin', # only used in DEBUG mode 'django_nose', diff --git a/lms/static/sass/course/courseware/_courseware.scss b/lms/static/sass/course/courseware/_courseware.scss index e7527fc04d3..cbcfa0849c7 100644 --- a/lms/static/sass/course/courseware/_courseware.scss +++ b/lms/static/sass/course/courseware/_courseware.scss @@ -647,17 +647,3 @@ section.self-assessment { font-weight: bold; } } - -section.foldit { - table { - margin-top: ($baseline/2); - } - th { - text-align: center; - } - td { - padding-left: ($baseline/4); - padding-right: ($baseline/4); - - } -} diff --git a/lms/templates/foldit.html b/lms/templates/foldit.html deleted file mode 100644 index 2a8271cc62a..00000000000 --- a/lms/templates/foldit.html +++ /dev/null @@ -1,12 +0,0 @@ -<section class="foldit"> - - % if show_basic: - ${folditbasic} - % endif - - - % if show_leader: - ${folditchallenge} - % endif - -</section> diff --git a/lms/templates/folditbasic.html b/lms/templates/folditbasic.html deleted file mode 100644 index 909ffb3b01d..00000000000 --- a/lms/templates/folditbasic.html +++ /dev/null @@ -1,33 +0,0 @@ -<%! -from django.utils.translation import ugettext as _ -from util.date_utils import get_default_time_display -%> -<div class="folditbasic"> -<p><strong>${_("Due:")}</strong> ${get_default_time_display(due)} - -<p> - <strong>${_("Status:")}</strong> - % if success: - ${_('You have successfully gotten to level {goal_level}.').format(goal_level=goal_level)}' - % else: - ${_('You have not yet gotten to level {goal_level}.').format(goal_level=goal_level)} - % endif -</p> - -<h3>${_("Completed puzzles")}</h3> - -<table> - <tr> - <th>${_("Level")}</th> - <th>${_("Submitted")}</th> - </tr> - % for puzzle in completed: - <tr> - <td>${'{0}-{1}'.format(puzzle['set'], puzzle['subset'])}</td> - <td>${puzzle['created'].strftime('%Y-%m-%d %H:%M')}</td> - </tr> - % endfor -</table> - -</br> -</div> diff --git a/lms/templates/folditchallenge.html b/lms/templates/folditchallenge.html deleted file mode 100644 index 36e8f0caee1..00000000000 --- a/lms/templates/folditchallenge.html +++ /dev/null @@ -1,18 +0,0 @@ -<%! from django.utils.translation import ugettext as _ %> - -<div class="folditchallenge"> - <h3>${_("Puzzle Leaderboard")}</h3> - - <table> - <tr> - <th>${_("User")}</th> - <th>${_("Score")}</th> - </tr> - % for pair in top_scores: - <tr> - <td>${pair[0]}</td> - <td>${pair[1]}</td> - </tr> - % endfor - </table> -</div> diff --git a/lms/urls.py b/lms/urls.py index 0f32ac6de80..fdd13a71059 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -651,12 +651,6 @@ if settings.FEATURES.get('RUN_AS_ANALYTICS_SERVER_ENABLED'): url(r'^edinsights_service/', include('edinsights.core.urls')), ) -# FoldIt views -urlpatterns += ( - # The path is hardcoded into their app... - url(r'^comm/foldit_ops', 'foldit.views.foldit_ops', name="foldit_ops"), -) - if settings.FEATURES.get('ENABLE_DEBUG_RUN_PYTHON'): urlpatterns += ( url(r'^debug/run_python$', 'debug.views.run_python'), -- GitLab