Commit 449d665b by Rocky Duan

Merge branch 'master' of github.com:MITx/mitx into merge

parents 30877250 49d36746
......@@ -90,6 +90,16 @@ TEMPLATE_CONTEXT_PROCESSORS = (
################################# Jasmine ###################################
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
#################### CAPA External Code Evaluation #############################
XQUEUE_INTERFACE = {
'url': 'http://localhost:8888',
'django_auth': {'username': 'local',
'password': 'local'},
'basic_auth': None,
}
################################# Middleware ###################################
# List of finder classes that know how to find static files in
# various locations.
......
......@@ -7,13 +7,10 @@ import logging
import requests
import time
# TODO: Collection of parameters to be hooked into rest of edX system
XQUEUE_LMS_AUTH = { 'username': 'LMS',
'password': 'PaloAltoCA' }
XQUEUE_URL = 'http://xqueue.edx.org'
log = logging.getLogger('mitx.' + __name__)
def make_hashkey(seed=None):
'''
Generate a string key by hashing
......@@ -58,15 +55,15 @@ def parse_xreply(xreply):
return (return_code, content)
class XqueueInterface:
class XQueueInterface(object):
'''
Interface to the external grading system
'''
def __init__(self, url=XQUEUE_URL, auth=XQUEUE_LMS_AUTH):
def __init__(self, url, django_auth, requests_auth=None):
self.url = url
self.auth = auth
self.session = requests.session()
self.auth = django_auth
self.session = requests.session(auth=requests_auth)
def send_to_queue(self, header, body, file_to_upload=None):
'''
......@@ -117,5 +114,3 @@ class XqueueInterface:
return (1, 'unexpected HTTP status code [%d]' % r.status_code)
return parse_xreply(r.text)
qinterface = XqueueInterface()
......@@ -3,7 +3,7 @@
<html xmlns="http://www.w3.org/1999/xhtml">
{% spaceless %}
<head>
<title>{% block title %}{% endblock %} - MITX 6.002</title>
<title>{% block title %}{% endblock %}</title>
{% include "meta/html_head_meta.html" %}
<link rel="shortcut icon" href="{{ settings.SITE_FAVICON|media }}" />
{% include "meta/html_head_stylesheets.html" %}
......
......@@ -33,8 +33,14 @@
<!-- Quick fix -- we should reference askbot jquery properly -->
<script
type="text/javascript"
src="{{ settings.STATIC_URL }}/js/askbot_jquery.min.js"
src="{{'/js/jquery-1.4.3.js'|media}}"
></script>
{#
<script
type="text/javascript"
src="{{ settings.STATIC_ROOT }}/js/askbot_jquery.min.js"
></script>
#}
<!-- History.js -->
<script type='text/javascript' src="{{"/js/jquery.history.js"|media }}"></script>
<script type='text/javascript' src="{{"/js/utils.js"|media }}"></script>
......
<header class="global" aria-label="Global Navigation">
<nav>
<h1 class="logo"><a href="${reverse('root')}"></a></h1>
<h1 class="logo"><a href="{% url root %}"></a></h1>
<ol class="left">
<li class="primary">
<a href="${reverse('courses')}">Find Courses</a>
<a href="{% url courses %}">Find Courses</a>
</li>
</ol>
<ol class="user">
<li class="primary">
<a href="${reverse('dashboard')}" class="user-link">
<a href="{% url dashboard %}" class="user-link">
<span class="avatar"></span>
${user.username}
{{user.username}}
</a>
</li>
<li class="primary">
<a href="#" class="dropdown">&#9662</a>
<ul class="dropdown-menu">
## <li><a href="#">Account Settings</a></li>
<li><a href="${reverse('help_edx')}">Help</a></li>
<li><a href="${reverse('logout')}">Log Out</a></li>
{# <li><a href="#">Account Settings</a></li> #}
<li><a href="{% url help_edx %}">Help</a></li>
<li><a href="{% url logout %}">Log Out</a></li>
</ul>
</li>
</ol>
......
......@@ -3,19 +3,19 @@
<nav>
<section class="top">
<section class="primary">
<a href="${reverse('root')}" class="logo"></a>
<a href="${reverse('courses')}">Find Courses</a>
<a href="${reverse('about_edx')}">About</a>
<a href="{% url root %}" class="logo"></a>
<a href="{% url courses %}">Find Courses</a>
<a href="{% url about_edx %}">About</a>
<a href="http://edxonline.tumblr.com/">Blog</a>
<a href="${reverse('jobs')}">Jobs</a>
<a href="${reverse('contact')}">Contact</a>
<a href="{% url jobs %}">Jobs</a>
<a href="{% url contact %}">Contact</a>
</section>
<section class="social">
<a href="http://youtube.com/user/edxonline"><img src="${static.url('images/social/youtube-sharing.png')}" /></a>
<a href="https://plus.google.com/108235383044095082735"><img src="${static.url('images/social/google-plus-sharing.png')}" /></a>
<a href="http://www.facebook.com/EdxOnline"><img src="${static.url('images/social/facebook-sharing.png')}" /></a>
<a href="https://twitter.com/edXOnline"><img src="${static.url('images/social/twitter-sharing.png')}" /></a>
<a href="http://youtube.com/user/edxonline"><img src='{{"images/social/youtube-sharing.png"|media}}' /></a>
<a href="https://plus.google.com/108235383044095082735"><img src="{{('images/social/google-plus-sharing.png'|media)}}" /></a>
<a href="http://www.facebook.com/EdxOnline"><img src="{{'images/social/facebook-sharing.png'|media}}" /></a>
<a href="https://twitter.com/edXOnline"><img src="{{'images/social/twitter-sharing.png'|media}}" /></a>
</section>
</section>
......@@ -25,10 +25,10 @@
</section>
<section class="secondary">
<a href="${reverse('tos')}">Terms of Service</a>
<a href="${reverse('privacy_edx')}">Privacy Policy</a>
<a href="${reverse('honor')}">Honor Code</a>
<a href="${reverse('help_edx')}">Help</a>
<a href="{% url tos %}">Terms of Service</a>
<a href="{% url privacy_edx %}">Privacy Policy</a>
<a href="{% url honor %}">Honor Code</a>
<a href="{% url help_edx %}">Help</a>
</section>
</section>
......
......@@ -7,9 +7,9 @@ from django.http import Http404
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from capa.xqueue_interface import XQueueInterface
from django.contrib.auth.models import User
from xmodule.modulestore.django import modulestore
from capa.xqueue_interface import qinterface
from mitxmako.shortcuts import render_to_string
from models import StudentModule, StudentModuleCache
from static_replace import replace_urls
......@@ -19,10 +19,23 @@ from xmodule_modifiers import replace_static_urls, add_histogram, wrap_xmodule
from courseware.courses import (has_staff_access_to_course,
has_staff_access_to_location)
from requests.auth import HTTPBasicAuth
log = logging.getLogger("mitx.courseware")
if settings.XQUEUE_INTERFACE['basic_auth'] is not None:
requests_auth = HTTPBasicAuth(*settings.XQUEUE_INTERFACE['basic_auth'])
else:
requests_auth = None
xqueue_interface = XQueueInterface(
settings.XQUEUE_INTERFACE['url'],
settings.XQUEUE_INTERFACE['django_auth'],
requests_auth,
)
def make_track_function(request):
'''
Make a tracking function that logs what happened.
......@@ -172,9 +185,9 @@ def get_module(user, request, location, student_module_cache, position=None):
# TODO: Queuename should be derived from 'course_settings.json' of each course
xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course
xqueue = { 'interface': qinterface,
'callback_url': xqueue_callback_url,
'default_queuename': xqueue_default_queuename.replace(' ','_') }
xqueue = {'interface': xqueue_interface,
'callback_url': xqueue_callback_url,
'default_queuename': xqueue_default_queuename.replace(' ', '_')}
def _get_module(location):
return get_module(user, request, location,
......
......@@ -54,3 +54,5 @@ AWS_ACCESS_KEY_ID = AUTH_TOKENS["AWS_ACCESS_KEY_ID"]
AWS_SECRET_ACCESS_KEY = AUTH_TOKENS["AWS_SECRET_ACCESS_KEY"]
DATABASES = AUTH_TOKENS['DATABASES']
XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE']
......@@ -225,8 +225,7 @@ STATIC_ROOT = ENV_ROOT / "staticfiles"
STATICFILES_DIRS = [
COMMON_ROOT / "static",
PROJECT_ROOT / "static",
ASKBOT_ROOT / "askbot" / "skins",
PROJECT_ROOT / "askbot" / "skins",
]
if os.path.isdir(DATA_DIR):
STATICFILES_DIRS += [
......
......@@ -53,6 +53,15 @@ CACHES = {
}
}
XQUEUE_INTERFACE = {
"url": "http://xqueue.sandbox.edx.org",
"django_auth": {
"username": "lms",
"password": "***REMOVED***"
},
"basic_auth": ('anant', 'agarwal'),
}
# Make the keyedcache startup warnings go away
CACHE_TIMEOUT = 0
......
......@@ -41,3 +41,6 @@ def course_db_for(course_id):
}
}
def askbot_url_for(course_id):
return "courses/{0}/discussions/".format(course_id)
from .courses import *
DATABASES = course_db_for('HarvardX/CS50x/2012')
\ No newline at end of file
DATABASES = course_db_for('HarvardX/CS50x/2012')
ASKBOT_URL = askbot_url_for("HarvardX/CS50x/2012")
\ No newline at end of file
from .courses import *
DATABASES = course_db_for('MITx/6.002x/2012_Fall')
\ No newline at end of file
DATABASES = course_db_for('MITx/6.002x/2012_Fall')
ASKBOT_URL = askbot_url_for("MITx/6.002x/2012_Fall")
......@@ -50,6 +50,16 @@ COMMON_TEST_DATA_ROOT = COMMON_ROOT / "test" / "data"
GITHUB_REPO_ROOT = ENV_ROOT / "data"
XQUEUE_INTERFACE = {
"url": "http://xqueue.sandbox.edx.org",
"django_auth": {
"username": "lms",
"password": "***REMOVED***"
},
"basic_auth": ('anant', 'agarwal'),
}
# TODO (cpennington): We need to figure out how envs/test.py can inject things
# into common.py so that we don't have to repeat this sort of thing
STATICFILES_DIRS = [
......
var Gradebook = function($element) {
var _this = this;
var $element = $element;
var $grades = $element.find('.grades');
var $gradeTable = $element.find('.grade-table');
var $leftShadow = $('<div class="left-shadow"></div>');
var $rightShadow = $('<div class="right-shadow"></div>');
var tableHeight = $gradeTable.height();
var maxScroll = $gradeTable.width() - $grades.width();
var $body = $('body');
var mouseOrigin;
var tableOrigin;
var startDrag = function(e) {
mouseOrigin = e.pageX;
tableOrigin = $gradeTable.position().left;
$body.css('-webkit-user-select', 'none');
$body.bind('mousemove', moveDrag);
$body.bind('mouseup', stopDrag);
};
var moveDrag = function(e) {
var offset = e.pageX - mouseOrigin;
var targetLeft = clamp(tableOrigin + offset, -maxScroll, 0);
updateHorizontalPosition(targetLeft);
setShadows(targetLeft);
};
var stopDrag = function(e) {
$body.css('-webkit-user-select', 'auto');
$body.unbind('mousemove', moveDrag);
$body.unbind('mouseup', stopDrag);
};
var setShadows = function(left) {
var padding = 30;
var leftPercent = clamp(-left / padding, 0, 1);
$leftShadow.css('opacity', leftPercent);
var rightPercent = clamp((maxScroll + left) / padding, 0, 1);
$rightShadow.css('opacity', rightPercent);
};
var clamp = function(val, min, max) {
if(val > max) return max;
if(val < min) return min;
return val;
};
var updateWidths = function(e) {
maxScroll = $gradeTable.width() - $grades.width();
var targetLeft = clamp($gradeTable.position().left, -maxScroll, 0);
updateHorizontalPosition(targetLeft);
setShadows(targetLeft);
}
var updateHorizontalPosition = function(left) {
$gradeTable.css({
'left': left + 'px'
});
}
$leftShadow.css('height', tableHeight + 'px');
$rightShadow.css('height', tableHeight + 'px');
$grades.append($leftShadow).append($rightShadow);
setShadows(0);
$grades.css('height', tableHeight);
$gradeTable.bind('mousedown', startDrag);
$(window).bind('resize', updateWidths);
}
\ No newline at end of file
......@@ -10,7 +10,7 @@
@import 'shared/tooltips';
// Course base / layout styles
@import 'course/layout/courseware_subnav';
@import 'course/layout/courseware_header';
@import 'course/base/base';
@import 'course/base/extends';
@import 'module/module-styles.scss';
......
$cell-border-color: #e1e1e1;
$table-border-color: #c8c8c8;
div.gradebook-wrapper {
@extend .table-wrapper;
section.gradebook-content {
@extend .content;
.student-search {
padding: 0 20px 0 15px;
}
.student-search-field {
width: 100%;
height: 27px;
padding: 0 15px 0 35px;
box-sizing: border-box;
border-radius: 13px;
border: 1px solid $table-border-color;
background: url(../images/search-icon.png) no-repeat 9px center #f6f6f6;
font-family: $sans-serif;
font-size: 11px;
@include box-shadow(0 1px 4px rgba(0, 0, 0, .12) inset);
outline: none;
@include transition(border-color .15s);
&::-webkit-input-placeholder,
&::-moz-input-placeholder {
font-style: italic;
}
&:focus {
border-color: #1d9dd9;
}
}
.student-table {
float: left;
// width: 264px;
width: 24%;
border-radius: 3px 0 0 3px;
color: #3c3c3c;
th {
height: 50px;
}
tr:first-child td {
border-top: 1px solid $table-border-color;
border-radius: 5px 0 0 0;
}
tr:last-child td {
border-bottom: 1px solid $table-border-color;
border-radius: 0 0 0 5px;
}
td {
height: 50px;
padding-left: 20px;
border-bottom: 1px solid $cell-border-color;
border-left: 1px solid $table-border-color;
background: #f3f3f3;
font-size: 13px;
line-height: 50px;
}
tr:nth-child(odd) td {
background-color: #fbfbfb;
}
}
.grades {
position: relative;
float: left;
width: 76%;
overflow: hidden;
.left-shadow,
.right-shadow {
position: absolute;
top: 0;
z-index: 9999;
width: 20px;
pointer-events: none;
}
.left-shadow {
left: 0;
background: -webkit-linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -webkit-linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
}
.right-shadow {
right: 0;
background: -webkit-linear-gradient(right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -webkit-linear-gradient(right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
}
}
.grade-table {
position: absolute;
top: 0;
left: 0;
width: 1000px;
cursor: move;
-webkit-transition: none;
-webkit-user-select: none;
user-select: none;
td,
th {
width: 50px;
text-align: center;
}
thead th {
position: relative;
height: 50px;
background: -webkit-linear-gradient(top, $cell-border-color, #ddd);
font-size: 10px;
line-height: 10px;
font-weight: bold;
text-align: center;
box-shadow: 0 1px 0 $table-border-color inset, 0 2px 0 rgba(255, 255, 255, .7) inset;
&:before {
content: '';
display: block;
position: absolute;
left: 0;
top: 0;
z-index: 9999;
width: 1px;
height: 100%;
background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0) 30%, rgba(0, 0, 0, .15));
}
&:first-child {
border-radius: 5px 0 0 0;
box-shadow: 1px 1px 0 $table-border-color inset, 1px 2px 0 rgba(255, 255, 255, .7) inset;
&:before {
display: hidden;
}
}
&:last-child {
border-radius: 0 3px 0 0;
box-shadow: -1px 1px 0 $table-border-color inset, -1px 2px 0 rgba(255, 255, 255, .7) inset;
}
.assignment {
margin: 9px 0;
}
.type,
.number,
.max {
display: block;
}
.max {
height: 12px;
background: -webkit-linear-gradient(top, #c6c6c6, #bababa);
font-size: 9px;
line-height: 12px;
color: #fff;
}
}
tr {
border-right: 1px solid $table-border-color;
}
tr:first-child td {
border-top: 1px solid $table-border-color;
}
tr:last-child td {
border-bottom: 1px solid $table-border-color;
}
td {
height: 50px;
border-bottom: 1px solid $cell-border-color;
background: #f3f3f3;
font-size: 13px;
line-height: 50px;
border-left: 1px solid $cell-border-color;
}
tr:nth-child(odd) td {
background-color: #fbfbfb;
}
}
h1 {
@extend .top-header;
}
}
}
\ No newline at end of file
}
......@@ -173,29 +173,3 @@ h1.top-header {
@include transition( all, .2s, $ease-in-out-quad);
}
.global {
.find-courses-button {
display: none;
}
h2 {
display: block;
float: left;
font-size: 0.9em;
font-weight: 600;
letter-spacing: 0;
line-height: 40px;
overflow: hidden;
text-overflow: ellipsis;
text-shadow: 0 1px 0 #fff;
text-transform: none;
white-space: nowrap;
width: 700px;
.provider {
font: inherit;
font-weight: bold;
color: #6d6d6d;
}
}
}
nav.course-material {
@include clearfix;
@include box-sizing(border-box);
background: #f6f6f6;
border-bottom: 1px solid rgb(200,200,200);
margin: 0px auto 0px;
padding: 0px;
width: 100%;
.inner-wrapper {
margin: 0 auto;
max-width: 1200px;
width: flex-grid(12);
}
ol.course-tabs {
@include border-top-radius(4px);
@include clearfix;
padding: 10px 0 0 0;
li {
float: left;
list-style: none;
a {
color: darken($lighter-base-font-color, 20%);
display: block;
text-align: center;
padding: 8px 13px 12px;
font-size: 14px;
font-weight: 400;
text-decoration: none;
text-shadow: 0 1px rgb(255,255,255);
&:hover {
color: $base-font-color;
}
&.active {
background: rgb(255,255,255);
border: 1px solid rgb(200,200,200);
border-bottom: 0px;
@include border-top-radius(4px);
@include box-shadow(0 2px 0 0 rgba(255,255,255, 1));
color: $blue;
}
}
}
}
}
.course-content {
margin-top: 30px;
.courseware {
min-height: 300px;
}
}
.global {
.find-courses-button {
display: none;
}
h2 {
display: block;
width: 700px;
float: left;
font-size: 0.9em;
font-weight: 600;
line-height: 40px;
letter-spacing: 0;
text-transform: none;
text-shadow: 0 1px 0 #fff;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
.provider {
font: inherit;
font-weight: bold;
color: #6d6d6d;
}
}
}
\ No newline at end of file
......@@ -6,6 +6,7 @@
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.stack.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.symbol.js')}"></script>
<script type="text/javascript" src="${static.url('js/jquery.gradebook.js')}"></script>
</%block>
<%block name="headextra">
......@@ -19,6 +20,12 @@
.grade_None {color:LightGray;}
</style>
<script type="text/javascript">
$(document).ready(function() {
var gradebook = new Gradebook($('.gradebook-content'));
});
</script>
</%block>
<%include file="course_navigation.html" args="active_page=''" />
......@@ -28,50 +35,75 @@
<section class="gradebook-content">
<h1>Gradebook</h1>
%if len(students) > 0:
<table>
<%
templateSummary = students[0]['grade_summary']
%>
<table class="student-table">
<thead>
<tr>
<th>
<form class="student-search">
<input type="search" class="student-search-field" placeholder="Search students" />
</form>
</th>
</tr>
</thead>
<tbody>
%for student in students:
<tr>
<td>
<a href="${reverse('student_profile', kwargs=dict(course_id=course_id, student_id=student['id']))}">${student['username']}</a>
</td>
</tr>
%endfor
</tbody>
</table>
<tr> <!-- Header Row -->
<th>Student</th>
%for section in templateSummary['section_breakdown']:
<th>${section['label']}</th>
%endfor
<th>Total</th>
</tr>
<%def name="percent_data(fraction)">
%if len(students) > 0:
<div class="grades">
<table class="grade-table">
<%
letter_grade = 'None'
if fraction > 0:
letter_grade = 'F'
for grade in ['A', 'B', 'C']:
if fraction >= course.grade_cutoffs[grade]:
letter_grade = grade
break
data_class = "grade_" + letter_grade
templateSummary = students[0]['grade_summary']
%>
<td class="${data_class}" data-percent="${fraction}">${ "{0:.0f}".format( 100 * fraction ) }</td>
</%def>
%for student in students:
<tr>
<td><a href="${reverse('student_profile',
kwargs=dict(course_id=course_id,
student_id=student['id']))}">
${student['username']}</a></td>
%for section in student['grade_summary']['section_breakdown']:
${percent_data( section['percent'] )}
%endfor
<th>${percent_data( student['grade_summary']['percent'])}</th>
</tr>
%endfor
</table>
<thead>
<tr> <!-- Header Row -->
%for section in templateSummary['section_breakdown']:
<th>${section['label']}</th>
%endfor
<th>Total</th>
</tr>
</thead>
<%def name="percent_data(fraction)">
<%
letter_grade = 'None'
if fraction > 0:
letter_grade = 'F'
for grade in ['A', 'B', 'C']:
if fraction >= course.grade_cutoffs[grade]:
letter_grade = grade
break
data_class = "grade_" + letter_grade
%>
<td class="${data_class}" data-percent="${fraction}">${ "{0:.0f}".format( 100 * fraction ) }</td>
</%def>
<tbody>
%for student in students:
<tr>
%for section in student['grade_summary']['section_breakdown']:
${percent_data( section['percent'] )}
%endfor
<td>${percent_data( student['grade_summary']['percent'])}</td>
</tr>
%endfor
</tbody>
</table>
</div>
%endif
</section>
</div>
</section>
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