Merge branch 'master' into feature/tomg/fall-design

This commit is contained in:
Tom Giannattasio
2012-08-21 14:50:15 -04:00
124 changed files with 11555 additions and 103 deletions

View File

@@ -0,0 +1,19 @@
##
## One-off script to sync all user information to the discussion service (later info will be synced automatically)
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
import comment_client as cc
class Command(BaseCommand):
help = \
'''
Sync all user ids, usernames, and emails to the discussion
service'''
def handle(self, *args, **options):
for user in User.objects.all().iterator():
cc_user = cc.User.from_django_user(user)
cc_user.save()

View File

@@ -45,6 +45,15 @@ from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from django_countries import CountryField
from django.db.models.signals import post_save
from django.dispatch import receiver
from functools import partial
import comment_client as cc
import logging
from xmodule.modulestore.django import modulestore
#from cache_toolbox import cache_model, cache_relation
@@ -254,8 +263,18 @@ def add_user_to_default_group(user, group):
utg.users.add(User.objects.get(username=user))
utg.save()
# @receiver(post_save, sender=User)
def update_user_information(sender, instance, created, **kwargs):
try:
cc_user = cc.User.from_django_user(instance)
cc_user.save()
except Exception as e:
log = logging.getLogger("mitx.discussion")
log.error(unicode(e))
log.error("update user info to discussion failed for user with id: " + str(instance.id))
########################## REPLICATION SIGNALS #################################
@receiver(post_save, sender=User)
# @receiver(post_save, sender=User)
def replicate_user_save(sender, **kwargs):
user_obj = kwargs['instance']
if not should_replicate(user_obj):
@@ -263,7 +282,7 @@ def replicate_user_save(sender, **kwargs):
for course_db_name in db_names_to_replicate_to(user_obj.id):
replicate_user(user_obj, course_db_name)
@receiver(post_save, sender=CourseEnrollment)
# @receiver(post_save, sender=CourseEnrollment)
def replicate_enrollment_save(sender, **kwargs):
"""This is called when a Student enrolls in a course. It has to do the
following:
@@ -289,12 +308,12 @@ def replicate_enrollment_save(sender, **kwargs):
user_profile = UserProfile.objects.get(user_id=enrollment_obj.user_id)
replicate_model(UserProfile.save, user_profile, enrollment_obj.user_id)
@receiver(post_delete, sender=CourseEnrollment)
# @receiver(post_delete, sender=CourseEnrollment)
def replicate_enrollment_delete(sender, **kwargs):
enrollment_obj = kwargs['instance']
return replicate_model(CourseEnrollment.delete, enrollment_obj, enrollment_obj.user_id)
@receiver(post_save, sender=UserProfile)
# @receiver(post_save, sender=UserProfile)
def replicate_userprofile_save(sender, **kwargs):
"""We just updated the UserProfile (say an update to the name), so push that
change to all Course DBs that we're enrolled in."""
@@ -404,4 +423,3 @@ def should_replicate(instance):
.format(instance))
return False
return True

View File

@@ -8,6 +8,7 @@ import logging
from datetime import datetime
from django.test import TestCase
from nose.plugins.skip import SkipTest
from .models import User, UserProfile, CourseEnrollment, replicate_user, USER_FIELDS_TO_COPY
@@ -22,6 +23,7 @@ class ReplicationTest(TestCase):
def test_user_replication(self):
"""Test basic user replication."""
raise SkipTest()
portal_user = User.objects.create_user('rusty', 'rusty@edx.org', 'fakepass')
portal_user.first_name='Rusty'
portal_user.last_name='Skids'
@@ -80,6 +82,7 @@ class ReplicationTest(TestCase):
def test_enrollment_for_existing_user_info(self):
"""Test the effect of Enrolling in a class if you've already got user
data to be copied over."""
raise SkipTest()
# Create our User
portal_user = User.objects.create_user('jack', 'jack@edx.org', 'fakepass')
portal_user.first_name = "Jack"
@@ -143,6 +146,8 @@ class ReplicationTest(TestCase):
def test_enrollment_for_user_info_after_enrollment(self):
"""Test the effect of modifying User data after you've enrolled."""
raise SkipTest()
# Create our User
portal_user = User.objects.create_user('patty', 'patty@edx.org', 'fakepass')
portal_user.first_name = "Patty"

View File

@@ -29,6 +29,7 @@ Each input type takes the xml tree as 'element', the previous answer as 'value',
import logging
import re
import shlex # for splitting quoted strings
import json
from lxml import etree
import xml.sax.saxutils as saxutils
@@ -336,6 +337,11 @@ def filesubmission(element, value, status, render_template, msg=''):
Upload a single file (e.g. for programming assignments)
'''
eid = element.get('id')
escapedict = {'"': '"'}
allowed_files = json.dumps(element.get('allowed_files', '').split())
allowed_files = saxutils.escape(allowed_files, escapedict)
required_files = json.dumps(element.get('required_files', '').split())
required_files = saxutils.escape(required_files, escapedict)
# Check if problem has been queued
queue_len = 0
@@ -345,7 +351,8 @@ def filesubmission(element, value, status, render_template, msg=''):
msg = 'Submitted to grader. (Queue length: %s)' % queue_len
context = { 'id': eid, 'state': status, 'msg': msg, 'value': value,
'queue_len': queue_len
'queue_len': queue_len, 'allowed_files': allowed_files,
'required_files': required_files
}
html = render_template("filesubmission.html", context)
return etree.XML(html)

View File

@@ -1,5 +1,5 @@
<section id="filesubmission_${id}" class="filesubmission">
<input type="file" name="input_${id}" id="input_${id}" value="${value}" multiple="multiple" /><br />
<input type="file" name="input_${id}" id="input_${id}" value="${value}" multiple="multiple" data-required_files="${required_files}" data-allowed_files="${allowed_files}"/><br />
% if state == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}"></span>
% elif state == 'correct':

View File

@@ -34,6 +34,7 @@ setup(
"video = xmodule.video_module:VideoDescriptor",
"videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"videosequence = xmodule.seq_module:SequenceDescriptor",
"discussion = xmodule.discussion_module:DiscussionDescriptor",
]
}
)

View File

@@ -68,7 +68,7 @@ class SemanticSectionDescriptor(XModuleDescriptor):
the child element
"""
xml_object = etree.fromstring(xml_data)
system.error_tracker("WARNING: the &lt;{0}> tag is deprecated. Please do not use in new content."
system.error_tracker("WARNING: the <{0}> tag is deprecated. Please do not use in new content."
.format(xml_object.tag))
if len(xml_object) == 1:

View File

@@ -0,0 +1,26 @@
from lxml import etree
from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
import json
class DiscussionModule(XModule):
def get_html(self):
context = {
'discussion_id': self.discussion_id,
}
return self.system.render_template('discussion/_discussion_module.html', context)
def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs)
if isinstance(instance_state, str):
instance_state = json.loads(instance_state)
xml_data = etree.fromstring(definition['data'])
self.discussion_id = xml_data.attrib['id']
self.title = xml_data.attrib['for']
self.discussion_category = xml_data.attrib['discussion_category']
class DiscussionDescriptor(RawDescriptor):
module_class = DiscussionModule

View File

@@ -160,24 +160,42 @@ class @Problem
max_filesize = 4*1000*1000 # 4 MB
file_too_large = false
file_not_selected = false
required_files_not_submitted = false
unallowed_file_submitted = false
errors = []
@inputs.each (index, element) ->
if element.type is 'file'
required_files = $(element).data("required_files")
allowed_files = $(element).data("allowed_files")
for file in element.files
if allowed_files.length != 0 and file.name not in allowed_files
unallowed_file_submitted = true
errors.push "You submitted #{file.name}; only #{allowed_files} are allowed."
if file.name in required_files
required_files.splice(required_files.indexOf(file.name), 1)
if file.size > max_filesize
file_too_large = true
alert 'Submission aborted! Your file "' + file.name '" is too large (max size: ' + max_filesize/(1000*1000) + ' MB)'
errors.push 'Your file "' + file.name '" is too large (max size: ' + max_filesize/(1000*1000) + ' MB)'
fd.append(element.id, file)
if element.files.length == 0
file_not_selected = true
fd.append(element.id, '') # In case we want to allow submissions with no file
if required_files.length != 0
required_files_not_submitted = true
errors.push "You did not submit the required files: #{required_files}."
else
fd.append(element.id, element.value)
if file_not_selected
alert 'Submission aborted! You did not select any files to submit'
errors.push 'You did not select any files to submit'
abort_submission = file_too_large or file_not_selected
if errors.length > 0
alert errors.join("\n")
abort_submission = file_too_large or file_not_selected or unallowed_file_submitted or required_files_not_submitted
settings =
type: "POST"

View File

@@ -3,6 +3,7 @@ class @Sequence
@el = $(element).find('.sequence')
@contents = @$('.seq_contents')
@id = @el.data('id')
@modx_url = @el.data('course_modx_root')
@initProgress()
@bind()
@render parseInt(@el.data('position'))
@@ -76,13 +77,14 @@ class @Sequence
if @position != new_position
if @position != undefined
@mark_visited @position
$.postWithPrefix "/modx/#{@id}/goto_position", position: new_position
modx_full_url = @modx_url + '/' + @id + '/goto_position'
$.postWithPrefix modx_full_url, position: new_position
@mark_active new_position
@$('#seq_content').html @contents.eq(new_position - 1).text()
XModule.loadModules('display', @$('#seq_content'))
MathJax.Hub.Queue(["Typeset", MathJax.Hub])
MathJax.Hub.Queue(["Typeset", MathJax.Hub, "seq_content"]) # NOTE: Actually redundant. Some other MathJax call also being performed
@position = new_position
@toggleArrows()
@hookUpProgressEvent()
@@ -91,7 +93,7 @@ class @Sequence
event.preventDefault()
new_position = $(event.target).data('element')
Logger.log "seq_goto", old: @position, new: new_position, id: @id
# On Sequence chage, destroy any existing polling thread
# for queued submissions, see ../capa/display.coffee
if window.queuePollerID