Skip to content

Commit

Permalink
Merge f51bcf0 into 3734847
Browse files Browse the repository at this point in the history
  • Loading branch information
jamalex committed Oct 16, 2014
2 parents 3734847 + f51bcf0 commit 567cfa2
Show file tree
Hide file tree
Showing 212 changed files with 13,410 additions and 9,628 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ writeup/
/TAGS
.tags
/kalite/i18n/static/
ghostdriver.log
/.vagrant/
ghostdriver.log
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ install:
script:
- python python-packages/fle_utils/tests.py
- python kalite/manage.py syncdb --migrate --noinput --traceback
- python kalite/manage.py test
- python kalite/manage.py test --traceback
- grunt jshint
notifications:
email:
Expand Down
32 changes: 32 additions & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

# Every Vagrant virtual environment requires a box to build off of.
config.vm.box = "trusty64"

# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
config.vm.network "forwarded_port", guest: 8008, host: 38008

# Create a private network, which allows host-only access to the machine
# using a specific IP.
config.vm.network "private_network", ip: "192.168.33.10"

# Create a public network, which generally matched to bridged network.
# Bridged networks make the machine appear as another physical device on
# your network.
# config.vm.network "public_network"

# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
config.vm.synced_folder ".", "/vagrant", type: "nfs"

end
Empty file added kalite/ab_testing/__init__.py
Empty file.
Empty file.
33 changes: 33 additions & 0 deletions kalite/ab_testing/data/conditions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
CONDITION_SETTINGS = {
"nalanda_control": {
"distributed.turn_off_motivational_features": True,
"distributed.turn_off_points_for_videos": True,
"distributed.turn_off_points_for_exercises": True,
"student_testing.turn_on_points_for_practice_exams": False,
"store.show_store_link_once_points_earned": False,
"distributed.front_page_welcome_message": "Try as hard as you can on each question in your practicing and and on the exams!",
},
"nalanda_input": {
"distributed.turn_off_motivational_features": False,
"distributed.turn_off_points_for_videos": True,
"distributed.turn_off_points_for_noncurrent_unit": True,
"student_testing.turn_on_points_for_practice_exams": False,
"store.show_store_link_once_points_earned": True,
"distributed.front_page_welcome_message": "This month you will earn points for the questions you answer correctly <b>in your practice exercises and quizzes</b>, and you will be able to exchange your points for rewards. For example Y points will earn you X stickers. Try as hard as you can on each question to earn rewards!",
},
"nalanda_output": {
"distributed.turn_off_motivational_features": True,
"distributed.turn_off_points_for_videos": True,
"distributed.turn_off_points_for_exercises": True,
"student_testing.turn_on_points_for_practice_exams": True,
"store.show_store_link_once_points_earned": True,
"distributed.front_page_welcome_message": "This month you will earn points for the questions you answer correctly <b>on your first unit test</b>. You will also be able to exchange your points for rewards. For example Y points will earn you X stickers. Try as hard as you can on the exam at the end of the month to earn rewards!",
},
"testing": {
"distributed.turn_off_motivational_features": False,
"distributed.turn_off_points_for_videos": False,
"distributed.turn_off_points_for_exercises": False,
"store.show_store_link_once_points_earned": True,
"distributed.front_page_welcome_message": "This month you will earn points for the questions you answer correctly <b>in your practice exercises and quizzes</b>, and you will be able to exchange your points for rewards. For example Y points will earn you X stickers. Try as hard as you can on each question to earn rewards!",
},
}
34 changes: 34 additions & 0 deletions kalite/ab_testing/data/groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
CONDITION_BY_FACILITY_AND_UNIT = {
"facility1": {
"1": "nalanda_control",
"2": "nalanda_input",
"3": "nalanda_output",
},
"facility2": {
"1": "nalanda_output",
"2": "nalanda_control",
"3": "nalanda_input",
},
"facility3": {
"1": "nalanda_input",
"2": "nalanda_output",
"3": "nalanda_control",
},
"Test": {
"1": "testing",
"2": "testing",
"3": "testing",
"4": "testing",
"5": "testing",
"6": "testing",
"7": "testing",
"8": "testing",
},
}

GRADE_BY_FACILITY = {
"facility1": 4,
"facility2": 5,
"facility3": 6,
"Test": 1,
}
40 changes: 40 additions & 0 deletions kalite/ab_testing/dynamic_assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from .data.groups import CONDITION_BY_FACILITY_AND_UNIT as CONDITION, GRADE_BY_FACILITY as GRADE
from .data.conditions import CONDITION_SETTINGS

from kalite.student_testing.utils import get_current_unit_settings_value

from kalite.dynamic_assets import DynamicSettingsBase, fields

class DynamicSettings(DynamicSettingsBase):
student_grade_level = fields.IntegerField(default=0)

# modify ds in-place. Use when you're modifying ds rather than defining new dynamic settings
def modify_dynamic_settings(ds, request=None, user=None):

user = user or request.session.get('facility_user')

if user and not user.is_teacher:

# determine the facility and unit for the current user
facility = user.facility
unit = get_current_unit_settings_value(facility.id)

# look up what condition the user is currently assigned to
condition = CONDITION.get(facility.id, CONDITION.get(facility.name, CONDITION.get(facility.id[0:8], {}))).get(str(unit), "")

# Set grade level on students to enable dynamic checking of student playlists within a unit.
# TODO (richard): (doge) Much hardcode. Such hack. So get rid. Wow.
ds["ab_testing"].student_grade_level = GRADE.get(facility.id, GRADE.get(facility.name, GRADE.get(facility.id[0:8], 0)))
ds["ab_testing"].unit = unit

# load the settings associated with the user's current condition
new_settings = CONDITION_SETTINGS.get(condition, {})

# merge the settings into the distributed settings (ds) object
for key, value in new_settings.items():
namespace, setting = key.split(".")
if namespace not in ds:
raise Exception("Could not modify setting '%s': the '%s' app has not defined a dynamic_assets.py file containing DynamicSettings." % (key, namespace))
if not hasattr(ds[namespace], setting):
raise Exception("Could not modify setting '%s': no such setting defined in the '%s' app's DynamicSettings." % (key, namespace))
setattr(ds[namespace], setting, value)
6 changes: 6 additions & 0 deletions kalite/ab_testing/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
try:
import local_settings
except ImportError:
local_settings = object()

INSTALLED_APPS = tuple()
16 changes: 16 additions & 0 deletions kalite/ab_testing/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""

from django.test import TestCase


class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)
111 changes: 111 additions & 0 deletions kalite/coachreports/api_resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from tastypie import fields
from tastypie.exceptions import NotFound, Unauthorized
from tastypie.resources import ModelResource, Resource

from django.utils.translation import ugettext as _

from kalite.shared.decorators import get_user_from_request
from .models import PlaylistProgress, PlaylistProgressDetail


class PlaylistParentResource(Resource):
"""
A parent resource that houses shared code between the two resources we actually use
in the API
"""

@classmethod
def permission_check(self, request):
"""
Require that the users are logged in, and that the user is the same student
whose data is being requested, an admin, or a teacher in that facility
"""
if getattr(request, "is_logged_in", False):
pass
else:
raise Unauthorized(_("You must be logged in to access this page."))

if getattr(request, "is_admin", False):
pass
else:
user = get_user_from_request(request=request)
if request.GET.get("user_id") == user.id:
pass
else:
raise Unauthorized(_("You requested information for a user that you are not authorized to view."))


class PlaylistProgressResource(PlaylistParentResource):

title = fields.CharField(attribute='title')
id = fields.CharField(attribute='id')
tag = fields.CharField(attribute='tag', null=True)
url = fields.CharField(attribute='url')
vid_pct_complete = fields.IntegerField(attribute='vid_pct_complete')
vid_pct_started = fields.IntegerField(attribute='vid_pct_started')
vid_status = fields.CharField(attribute='vid_status')
ex_pct_mastered = fields.IntegerField(attribute='ex_pct_mastered')
ex_pct_struggling = fields.IntegerField(attribute='ex_pct_struggling')
ex_pct_incomplete = fields.IntegerField(attribute='ex_pct_incomplete')
ex_status = fields.CharField(attribute='ex_status')
quiz_exists = fields.BooleanField(attribute='quiz_exists')
quiz_status = fields.CharField(attribute='quiz_status')
quiz_pct_score = fields.IntegerField(attribute='quiz_pct_score')
n_pl_videos = fields.IntegerField(attribute='n_pl_videos')
n_pl_exercises = fields.IntegerField(attribute='n_pl_exercises')

class Meta:
resource_name = "playlist_progress"
object_class = PlaylistProgress

def detail_uri_kwargs(self, bundle_or_obj):
kwargs = {}
if isinstance(bundle_or_obj, PlaylistProgress):
kwargs['pk'] = bundle_or_obj.id
else:
kwargs['pk'] = bundle_or_obj.obj.id

return kwargs

def get_object_list(self, request):
user_id = request.GET.get('user_id')
result = PlaylistProgress.user_progress(user_id=user_id)
return result

def obj_get_list(self, bundle, **kwargs):
self.permission_check(bundle.request)
return self.get_object_list(bundle.request)


class PlaylistProgressDetailResource(PlaylistParentResource):

kind = fields.CharField(attribute='kind')
status = fields.CharField(attribute='status')
title = fields.CharField(attribute='title')
score = fields.IntegerField(attribute='score')
path = fields.CharField(attribute='path')

class Meta:
resource_name = "playlist_progress_detail"
object_class = PlaylistProgressDetail

def detail_uri_kwargs(self, bundle_or_obj):
kwargs = {}
if isinstance(bundle_or_obj, PlaylistProgressDetail):
kwargs['pk'] = bundle_or_obj.id
else:
kwargs['pk'] = bundle_or_obj.obj.id

return kwargs

def get_object_list(self, request):
user_id = request.GET.get("user_id")
playlist_id = request.GET.get("playlist_id")
result = PlaylistProgressDetail.user_progress_detail(user_id=user_id, playlist_id=playlist_id)
if not result:
raise NotFound("User playlist progress details with user ID '%s' and playlist ID '%s' were not found." % (user_id, playlist_id))
return result

def obj_get_list(self, bundle, **kwargs):
self.permission_check(bundle.request)
return self.get_object_list(bundle.request)
12 changes: 9 additions & 3 deletions kalite/coachreports/api_urls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
from django.conf.urls import patterns, url, include

from .api_resources import PlaylistProgressResource, PlaylistProgressDetailResource


urlpatterns = patterns(__package__ + '.api_views',
# Non tasty API
url(r'data/$', 'api_data', {}, 'api_data'),
url(r'^data/(?P<xaxis>\w+)/(?P<yaxis>\w+)/$', 'api_data', {}, 'api_data_2'),
# url(r'friendly/$', 'api_friendly_names', {}, 'api_friendly_names'),
url(r'^get_topic_tree(?P<topic_path>.*)$', 'get_topic_tree_by_kinds', {}, 'get_topic_tree_by_kinds')
)
# url(r'friendly/$', 'api_friendly_names', {}, 'api_friendly_names'),
url(r'^get_topic_tree(?P<topic_path>.*)$', 'get_topic_tree_by_kinds', {}, 'get_topic_tree_by_kinds'),

# TastyPie API Urls
url(r'^', include(PlaylistProgressResource().urls)),
url(r'^', include(PlaylistProgressDetailResource().urls)),
)

0 comments on commit 567cfa2

Please sign in to comment.