Merge branch 'master' into feature/tomg/fall-design
This commit is contained in:
@@ -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()
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
@@ -68,7 +68,7 @@ class SemanticSectionDescriptor(XModuleDescriptor):
|
||||
the child element
|
||||
"""
|
||||
xml_object = etree.fromstring(xml_data)
|
||||
system.error_tracker("WARNING: the <{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:
|
||||
|
||||
26
common/lib/xmodule/xmodule/discussion_module.py
Normal file
26
common/lib/xmodule/xmodule/discussion_module.py
Normal 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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user