Merge branch 'master' of github.com:MITx/mitx into jarv/add-local-logging

This commit is contained in:
John Jarvis
2012-08-30 15:37:04 -04:00
63 changed files with 1379 additions and 471 deletions

View File

@@ -4,9 +4,12 @@ from django.utils.encoding import force_unicode
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
from django.template.loader import render_to_string
from wiki.editors.base import BaseEditor
from wiki.editors.markitup import MarkItUpAdminWidget
class CodeMirrorWidget(forms.Widget):
def __init__(self, attrs=None):
# The 'rows' and 'cols' attributes are required for HTML correctness.
@@ -18,9 +21,15 @@ class CodeMirrorWidget(forms.Widget):
def render(self, name, value, attrs=None):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, name=name)
return mark_safe(u'<div><textarea%s>%s</textarea></div>' % (flatatt(final_attrs),
conditional_escape(force_unicode(value))))
# TODO use the help_text field of edit form instead of rendering a template
return render_to_string('wiki/includes/editor_widget.html',
{'attrs': mark_safe(flatatt(final_attrs)),
'content': conditional_escape(force_unicode(value)),
})
class CodeMirror(BaseEditor):
@@ -50,5 +59,6 @@ class CodeMirror(BaseEditor):
"js/vendor/CodeMirror/xml.js",
"js/vendor/CodeMirror/mitx_markdown.js",
"js/wiki/CodeMirror.init.js",
"js/wiki/cheatsheet.js",
)

View File

@@ -17,6 +17,7 @@ log = logging.getLogger("mitx.courseware")
def yield_module_descendents(module):
stack = module.get_display_items()
stack.reverse()
while len(stack) > 0:
next_module = stack.pop()

View File

@@ -1,3 +1,4 @@
import hashlib
import json
import logging
import sys
@@ -143,8 +144,9 @@ def get_module(user, request, location, student_module_cache, course_id, positio
exists.
Arguments:
- user : current django User
- request : current django HTTPrequest
- user : User for whom we're getting the module
- request : current django HTTPrequest. Note: request.user isn't used for anything--all auth
and such works based on user.
- location : A Location-like object identifying the module to load
- student_module_cache : a StudentModuleCache
- course_id : the course_id in the context of which to load module
@@ -173,7 +175,13 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
if not has_access(user, descriptor, 'load'):
return None
#TODO Only check the cache if this module can possibly have state
# Anonymized student identifier
h = hashlib.md5()
h.update(settings.SECRET_KEY)
h.update(str(user.id))
anonymous_student_id = h.hexdigest()
# Only check the cache if this module can possibly have state
instance_module = None
shared_module = None
if user.is_authenticated():
@@ -197,6 +205,8 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
location=descriptor.location.url(),
dispatch=''),
)
# Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash.
ajax_url = ajax_url.rstrip('/')
# Fully qualified callback URL for external queueing system
xqueue_callback_url = '{proto}://{host}'.format(
@@ -217,7 +227,9 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
xqueue = {'interface': xqueue_interface,
'callback_url': xqueue_callback_url,
'default_queuename': xqueue_default_queuename.replace(' ', '_')}
'default_queuename': xqueue_default_queuename.replace(' ', '_'),
'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
}
def inner_get_module(location):
"""
@@ -241,7 +253,8 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
# a module is coming through get_html and is therefore covered
# by the replace_static_urls code below
replace_urls=replace_urls,
node_path=settings.NODE_PATH
node_path=settings.NODE_PATH,
anonymous_student_id=anonymous_student_id
)
# pass position specified in URL to module through ModuleSystem
system.set('position', position)
@@ -409,6 +422,10 @@ def modx_dispatch(request, dispatch, location, course_id):
'''
# ''' (fix emacs broken parsing)
# Check parameters and fail fast if there's a problem
if not Location.is_valid(location):
raise Http404("Invalid location")
# Check for submitted files and basic file size checks
p = request.POST.copy()
if request.FILES:

View File

@@ -411,8 +411,6 @@ class TestViewAuth(PageLoader):
"""list of urls that only instructors/staff should be able to see"""
urls = reverse_urls(['instructor_dashboard','gradebook','grade_summary'],
course)
urls.append(reverse('student_progress', kwargs={'course_id': course.id,
'student_id': user(self.student).id}))
return urls
def check_non_staff(course):
@@ -435,6 +433,17 @@ class TestViewAuth(PageLoader):
print 'checking for 200 on {0}'.format(url)
self.check_for_get_code(200, url)
# The student progress tab is not accessible to a student
# before launch, so the instructor view-as-student feature should return a 404 as well.
# TODO (vshnayder): If this is not the behavior we want, will need
# to make access checking smarter and understand both the effective
# user (the student), and the requesting user (the prof)
url = reverse('student_progress', kwargs={'course_id': course.id,
'student_id': user(self.student).id})
print 'checking for 404 on view-as-student: {0}'.format(url)
self.check_for_get_code(404, url)
# First, try with an enrolled student
print '=== Testing student access....'
self.login(self.student, self.password)

View File

@@ -325,14 +325,21 @@ def progress(request, course_id, student_id=None):
raise Http404
student = User.objects.get(id=int(student_id))
# NOTE: To make sure impersonation by instructor works, use
# student instead of request.user in the rest of the function.
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course_id, request.user, course)
course_module = get_module(request.user, request, course.location,
course_id, student, course)
course_module = get_module(student, request, course.location,
student_module_cache, course_id)
# The course_module should be accessible, but check anyway just in case something went wrong:
if course_module is None:
raise Http404("Course does not exist")
courseware_summary = grades.progress_summary(student, course_module,
course.grader, student_module_cache)
grade_summary = grades.grade(request.user, request, course, student_module_cache)
grade_summary = grades.grade(student, request, course, student_module_cache)
context = {'course': course,
'courseware_summary': courseware_summary,

View File

@@ -21,11 +21,17 @@ def dashboard(request):
if not request.user.is_staff:
raise Http404
query = "select count(user_id) as students, course_id from student_courseenrollment group by course_id order by students desc"
queries=[]
queries.append("select count(user_id) as students, course_id from student_courseenrollment group by course_id order by students desc;")
queries.append("select count(distinct user_id) as unique_students from student_courseenrollment;")
queries.append("select registrations, count(registrations) from (select count(user_id) as registrations from student_courseenrollment group by user_id) as registrations_per_user group by registrations;")
from django.db import connection
cursor = connection.cursor()
cursor.execute(query)
results = dictfetchall(cursor)
results =[]
for query in queries:
cursor.execute(query)
results.append(dictfetchall(cursor))
return HttpResponse(json.dumps(results, indent=4))

View File

@@ -0,0 +1,76 @@
#!/usr/bin/python
#
# File: manage_course_groups
#
# interactively list and edit membership in course staff and instructor groups
import os, sys, string, re
import datetime
from getpass import getpass
import json
import readline
from django.core.management.base import BaseCommand
from django.conf import settings
from django.contrib.auth.models import User, Group
#-----------------------------------------------------------------------------
# get all staff groups
class Command(BaseCommand):
help = "Manage course group membership, interactively."
def handle(self, *args, **options):
gset = Group.objects.all()
print "Groups:"
for cnt,g in zip(range(len(gset)), gset):
print "%d. %s" % (cnt,g)
gnum = int(raw_input('Choose group to manage (enter #): '))
group = gset[gnum]
#-----------------------------------------------------------------------------
# users in group
uall = User.objects.all()
if uall.count()<50:
print "----"
print "List of All Users: %s" % [str(x.username) for x in uall]
print "----"
else:
print "----"
print "There are %d users, which is too many to list" % uall.count()
print "----"
while True:
print "Users in the group:"
uset = group.user_set.all()
for cnt, u in zip(range(len(uset)), uset):
print "%d. %s" % (cnt, u)
action = raw_input('Choose user to delete (enter #) or enter usernames (comma delim) to add: ')
m = re.match('^[0-9]+$',action)
if m:
unum = int(action)
u = uset[unum]
print "Deleting user %s" % u
u.groups.remove(group)
else:
for uname in action.split(','):
try:
user = User.objects.get(username=action)
except Exception as err:
print "Error %s" % err
continue
print "adding %s to group %s" % (user, group)
user.groups.add(group)

View File

@@ -139,7 +139,7 @@ def gitreload(request, reload_dir=None):
ALLOWED_IPS = [] # allow none by default
if hasattr(settings,'ALLOWED_GITRELOAD_IPS'): # allow override in settings
ALLOWED_IPS = ALLOWED_GITRELOAD_IPS
ALLOWED_IPS = settings.ALLOWED_GITRELOAD_IPS
if not (ip in ALLOWED_IPS or 'any' in ALLOWED_IPS):
if request.user and request.user.is_staff:

View File

@@ -86,6 +86,9 @@ DEFAULT_GROUPS = []
# If this is true, random scores will be generated for the purpose of debugging the profile graphs
GENERATE_PROFILE_SCORES = False
# Used with XQueue
XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds
############################# SET PATH INFORMATION #############################
PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/lms
REPO_ROOT = PROJECT_ROOT.dirname()

View File

@@ -15,7 +15,7 @@ TEMPLATE_DEBUG = True
MITX_FEATURES['DISABLE_START_DATES'] = True
MITX_FEATURES['ENABLE_SQL_TRACKING_LOGS'] = True
MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = True
MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False # Enable to test subdomains--otherwise, want all courses to show up
MITX_FEATURES['SUBDOMAIN_BRANDING'] = True
WIKI_ENABLED = True
@@ -78,10 +78,10 @@ COURSE_LISTINGS = {
'MITx/3.091x/2012_Fall',
'MITx/6.002x/2012_Fall',
'MITx/6.00x/2012_Fall'],
'berkeley': ['BerkeleyX/CS169.1x/Cal_2012_Fall',
'BerkeleyX/CS188.1x/Cal_2012_Fall'],
'berkeley': ['BerkeleyX/CS169/fa12',
'BerkeleyX/CS188/fa12'],
'harvard': ['HarvardX/CS50x/2012H'],
'mit': [],
'mit': ['MITx/3.091/MIT_2012_Fall'],
'sjsu': ['MITx/6.002x-EE98/2012_Fall_SJSU'],
}
@@ -107,7 +107,7 @@ LMS_MIGRATION_ALLOWED_IPS = ['127.0.0.1']
MITX_FEATURES['AUTH_USE_OPENID'] = True
MITX_FEATURES['BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'] = True
INSTALLED_APPS += ('external_auth',)
INSTALLED_APPS += ('external_auth',)
INSTALLED_APPS += ('django_openid_auth',)
OPENID_CREATE_USERS = False

View File

@@ -58,7 +58,7 @@ XQUEUE_INTERFACE = {
},
"basic_auth": ('anant', 'agarwal'),
}
XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds
# 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

View File

@@ -124,7 +124,7 @@ if Backbone?
url = @model.urlFor('retrieve')
DiscussionUtil.safeAjax
$elem: $elem
$loading: $(event.target) if event
$loading: @$(".discussion-show-comments")
type: "GET"
url: url
success: (response, textStatus) =>

View File

@@ -169,7 +169,7 @@ if Backbone?
url = URI($elem.attr("action")).addSearch({text: @$(".search-input").val()})
@reload($elem, url)
sort: ->
sort: (event) ->
$elem = $(event.target)
url = $elem.attr("sort-url")
@reload($elem, url)

View File

@@ -3,9 +3,6 @@ $ ->
window.$$contents = {}
window.$$discussions = {}
$(".discussion-module").each (index, elem) ->
view = new DiscussionModuleView(el: elem)
$("section.discussion").each (index, elem) ->
discussionData = DiscussionUtil.getDiscussionData($(elem).attr("_id"))
discussion = new Discussion()

View File

@@ -1,9 +1,10 @@
$ ->
$.fn.extend
loading: ->
$(this).after("<span class='discussion-loading'></span>")
@$_loading = $("<span class='discussion-loading'></span>")
$(this).after(@$_loading)
loaded: ->
$(this).parent().children(".discussion-loading").remove()
@$_loading.remove()
class @DiscussionUtil

View File

@@ -0,0 +1,9 @@
$(document).ready(function () {
$('#cheatsheetLink').click(function() {
$('#cheatsheetModal').modal('show');
});
$('#cheatsheetModal .close-btn').click(function(e) {
$('#cheatsheetModal').modal('hide');
});
});

View File

@@ -27,6 +27,12 @@ body.cs188 {
margin-bottom: 1.416em;
}
.choicegroup {
input[type=checkbox], input[type=radio] {
margin-right: 5px;
}
}
}
}

View File

@@ -391,6 +391,18 @@ section.wiki {
line-height: 1.4em;
}
#div_id_content {
position: relative;
}
#hint_id_content {
position: absolute;
top: 10px;
right: 0%;
font-size: 12px;
text-align:right;
}
.CodeMirror {
background: #fafafa;
border: 1px solid #c8c8c8;
@@ -567,11 +579,73 @@ section.wiki {
background: #f00 !important;
}
#cheatsheetLink {
text-align: right;
display: float;
}
#cheatsheetModal {
width: 950px;
margin-left: -450px;
margin-top: -100px;
.left-column {
margin-right: 10px;
}
.left-column,
.right-column {
float: left;
width: 450px;
}
.close-btn {
display: block;
position: absolute;
top: -8px;
right: -8px;
width: 30px;
height: 30px;
border-radius: 30px;
border: 1px solid #ccc;
@include linear-gradient(top, #eee, #d2d2d2);
font-size: 22px;
line-height: 28px;
color: #333;
text-align: center;
@include box-shadow(0 1px 0 #fff inset, 0 1px 2px rgba(0, 0, 0, .2));
}
}
#cheatsheet-body {
background: #fff;
text-align: left;
padding: 20px;
font-size: 14px;
@include clearfix;
h3 {
font-weight: bold;
}
ul {
list-style: circle;
line-height: 1.4;
color: #333;
}
}
#cheatsheet-body section + section {
margin-top: 40px;
}
#cheatsheet-body pre{
color: #000;
text-align: left;
background: #eee;
padding: 10px;
font-size: 12px;
}
/*-----------------

View File

@@ -7,11 +7,11 @@
</div>
<div class="discussion-right-wrapper">
<ul class="admin-actions">
<li><a href="javascript:void(0)" class="admin-endorse">Endorse</a></li>
<li><a href="javascript:void(0)" class="admin-edit">Edit</a></li>
<li><a href="javascript:void(0)" class="admin-delete">Delete</a></li>
<li style="display: none;"><a href="javascript:void(0)" class="admin-endorse">Endorse</a></li>
<li style="display: none;"><a href="javascript:void(0)" class="admin-edit">Edit</a></li>
<li style="display: none;"><a href="javascript:void(0)" class="admin-delete">Delete</a></li>
{{#thread}}
<li><a href="javascript:void(0)" class="admin-openclose">{{close_thread_text}}</a></li>
<li style="display: none;"><a href="javascript:void(0)" class="admin-openclose">{{close_thread_text}}</a></li>
{{/thread}}
</ul>
{{#thread}}

View File

@@ -1,8 +1,8 @@
% if name is not UNDEFINED and name is not None:
<h1> ${display_name} </h1>
% if display_name is not UNDEFINED and display_name is not None:
<h2> ${display_name} </h2>
% endif
<div id="video_${id}" class="video" data-streams="${streams}" data-caption-data-dir="${data_dir}">
<div id="video_${id}" class="video" data-streams="${streams}" data-caption-data-dir="${data_dir}" data-show-captions="${show_captions}">
<div class="tc-wrapper">
<article class="video-wrapper">
<section class="video-player">

View File

@@ -27,21 +27,20 @@
});
}
</script>
{% addtoblock 'js' %}
{% comment %} These scripts load at the bottom of the body {% endcomment %}
<script src="{% static 'js/bootstrap-alert.js' %}"></script>
<script src="{% static 'js/bootstrap-collapse.js' %}"></script>
<script src="{% static 'js/bootstrap-modal.js' %}"></script>
{% with mathjax_mode='wiki' %}
{% include "mathjax_include.html" %}
{% endwith %}
{% endaddtoblock %}
{% endblock %}
@@ -64,11 +63,12 @@
</div>
{% endfor %}
{% endif %}
{% block wiki_contents %}{% endblock %}
{% endblock %}
</div>
</section>
{% endblock %}

View File

@@ -42,6 +42,7 @@
{% trans "Go back" %}
</a>
</div>
{% include "wiki/includes/cheatsheet.html" %}
</form>
</article>

View File

@@ -40,7 +40,10 @@
</a>
</div>
</div>
{% include "wiki/includes/cheatsheet.html" %}
</form>
{% endblock %}

View File

@@ -0,0 +1,56 @@
<div class="modal hide fade" id="cheatsheetModal">
<a href="#" class="close-btn">×</a>
<div id="cheatsheet-body" class="modal-body">
<div class="left-column">
<section>
<h2>Wiki Syntax Help</h2>
<p>This wiki uses <strong>Markdown</strong> for styling. There are several useful guides online. See any of the links below for in-depth details:</p>
<ul>
<li><a href="http://daringfireball.net/projects/markdown/basics" target="_blank">Markdown: Basics</a></li>
<li><a href="http://greg.vario.us/doc/markdown.txt" target="_blank">Quick Markdown Syntax Guide</a></li>
<li><a href="http://www.lowendtalk.com/discussion/6/miniature-markdown-guide" target="_blank">Miniature Markdown Guide</a></li>
</ul>
<p>To create a new wiki article, create a link to it. Clicking the link gives you the creation page.</p>
<pre>[Article Name](wiki:ArticleName)</pre>
</section>
<section>
<h3>edX Additions:</h3>
<pre>
circuit-schematic:</pre>
<pre>
$LaTeX Math Expression$</pre>
</section>
</div>
<div class="right-column">
<section>
<h3>Useful examples:</h3>
<pre>
http://wikipedia.org
[Wikipedia](http://wikipedia.org)
[edX Wiki](wiki:/edx/)</pre>
<pre>
Huge Header
===========</pre>
<pre>
Smaller Header
--------------</pre>
<pre>
*emphasis* or _emphasis_</pre>
<pre>
**strong** or __strong__</pre>
<pre>
- Unordered List
- Sub Item 1
- Sub Item 2</pre>
<pre>
1. Ordered
2. List</pre>
<pre>
> Quotes</pre>
</section>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,4 @@
<textarea {{ attrs }}>{{ content }}</textarea>
<p id="hint_id_content" class="help-block">
Markdown syntax is allowed. See the <a id="cheatsheetLink" href="#">cheatsheet</a> for help.
</p>

14
lms/wsgi.py Normal file
View File

@@ -0,0 +1,14 @@
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lms.envs.aws")
# This application object is used by the development server
# as well as any WSGI server configured to use this file.
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
from django.conf import settings
from xmodule.modulestore.django import modulestore
for store_name in settings.MODULESTORE:
modulestore(store_name)