Merge branch 'feature/bk_forum_int'
This commit is contained in:
@@ -36,7 +36,7 @@ class WikiRedirectTestCase(PageLoader):
|
||||
"""
|
||||
Test that requesting wiki URLs redirect properly to or out of classes.
|
||||
|
||||
An enrolled in student going from /courses/edX/toy/2012_Fall/profile
|
||||
An enrolled in student going from /courses/edX/toy/2012_Fall/progress
|
||||
to /wiki/some/fake/wiki/page/ will redirect to
|
||||
/courses/edX/toy/2012_Fall/wiki/some/fake/wiki/page/
|
||||
|
||||
@@ -48,10 +48,10 @@ class WikiRedirectTestCase(PageLoader):
|
||||
|
||||
self.enroll(self.toy)
|
||||
|
||||
referer = reverse("profile", kwargs={ 'course_id' : self.toy.id })
|
||||
referer = reverse("progress", kwargs={ 'course_id' : self.toy.id })
|
||||
destination = reverse("wiki:get", kwargs={'path': 'some/fake/wiki/page/'})
|
||||
|
||||
redirected_to = referer.replace("profile", "wiki/some/fake/wiki/page/")
|
||||
redirected_to = referer.replace("progress", "wiki/some/fake/wiki/page/")
|
||||
|
||||
resp = self.client.get( destination, HTTP_REFERER=referer)
|
||||
self.assertEqual(resp.status_code, 302 )
|
||||
@@ -77,11 +77,11 @@ class WikiRedirectTestCase(PageLoader):
|
||||
"""
|
||||
|
||||
course_wiki_home = reverse('course_wiki', kwargs={'course_id' : course.id})
|
||||
referer = reverse("profile", kwargs={ 'course_id' : self.toy.id })
|
||||
referer = reverse("progress", kwargs={ 'course_id' : self.toy.id })
|
||||
|
||||
resp = self.client.get(course_wiki_home, follow=True, HTTP_REFERER=referer)
|
||||
|
||||
course_wiki_page = referer.replace('profile', 'wiki/' + self.toy.wiki_slug + "/")
|
||||
course_wiki_page = referer.replace('progress', 'wiki/' + self.toy.wiki_slug + "/")
|
||||
|
||||
ending_location = resp.redirect_chain[-1][0]
|
||||
ending_status = resp.redirect_chain[-1][1]
|
||||
|
||||
@@ -25,7 +25,7 @@ def get_course_by_id(course_id):
|
||||
"""
|
||||
try:
|
||||
course_loc = CourseDescriptor.id_to_location(course_id)
|
||||
return modulestore().get_item(course_loc)
|
||||
return modulestore().get_instance(course_id, course_loc)
|
||||
except (KeyError, ItemNotFoundError):
|
||||
raise Http404("Course not found.")
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.conf import settings
|
||||
from models import StudentModuleCache
|
||||
from module_render import get_module, get_instance_module
|
||||
from xmodule import graders
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.graders import Score
|
||||
from models import StudentModule
|
||||
|
||||
@@ -63,8 +64,10 @@ def grade(student, request, course, student_module_cache=None):
|
||||
scores = []
|
||||
# TODO: We need the request to pass into here. If we could forgo that, our arguments
|
||||
# would be simpler
|
||||
course_id = CourseDescriptor.location_to_id(course.location)
|
||||
section_module = get_module(student, request,
|
||||
section_descriptor.location, student_module_cache)
|
||||
section_descriptor.location, student_module_cache,
|
||||
course_id)
|
||||
if section_module is None:
|
||||
# student doesn't have access to this module, or something else
|
||||
# went wrong.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import os.path
|
||||
|
||||
# THIS COMMAND IS OUT OF DATE
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
@@ -72,13 +74,17 @@ class Command(BaseCommand):
|
||||
|
||||
# TODO: use args as list of files to check. Fix loading to work for other files.
|
||||
|
||||
print "This command needs updating before use"
|
||||
return
|
||||
"""
|
||||
sample_user = User.objects.all()[0]
|
||||
|
||||
print "Attempting to load courseware"
|
||||
|
||||
# TODO (cpennington): Get coursename in a legitimate way
|
||||
course_location = 'i4x://edx/6002xs12/course/6.002_Spring_2012'
|
||||
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(sample_user, modulestore().get_item(course_location))
|
||||
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
|
||||
sample_user, modulestore().get_item(course_location))
|
||||
course = get_module(sample_user, None, course_location, student_module_cache)
|
||||
|
||||
to_run = [
|
||||
@@ -96,3 +102,4 @@ class Command(BaseCommand):
|
||||
print 'Courseware passes all checks!'
|
||||
else:
|
||||
print "Courseware fails some checks"
|
||||
"""
|
||||
|
||||
@@ -124,9 +124,11 @@ def check_roundtrip(course_dir):
|
||||
print "======== ideally there is no diff above this ======="
|
||||
|
||||
|
||||
def clean_xml(course_dir, export_dir):
|
||||
def clean_xml(course_dir, export_dir, force):
|
||||
(ok, course) = import_with_checks(course_dir)
|
||||
if ok:
|
||||
if ok or force:
|
||||
if not ok:
|
||||
print "WARNING: Exporting despite errors"
|
||||
export(course, export_dir)
|
||||
check_roundtrip(export_dir)
|
||||
else:
|
||||
@@ -138,11 +140,18 @@ class Command(BaseCommand):
|
||||
help = """Imports specified course.xml, validate it, then exports in
|
||||
a canonical format.
|
||||
|
||||
Usage: clean_xml PATH-TO-COURSE-DIR PATH-TO-OUTPUT-DIR
|
||||
Usage: clean_xml PATH-TO-COURSE-DIR PATH-TO-OUTPUT-DIR [force]
|
||||
|
||||
If 'force' is specified as the last argument, exports even if there
|
||||
were import errors.
|
||||
"""
|
||||
def handle(self, *args, **options):
|
||||
if len(args) != 2:
|
||||
n = len(args)
|
||||
if n < 2 or n > 3:
|
||||
print Command.help
|
||||
return
|
||||
|
||||
clean_xml(args[0], args[1])
|
||||
force = False
|
||||
if n == 3 and args[2] == 'force':
|
||||
force = True
|
||||
clean_xml(args[0], args[1], force)
|
||||
|
||||
@@ -71,13 +71,13 @@ def toc_for_course(user, request, course, active_chapter, active_section, course
|
||||
'''
|
||||
|
||||
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(user, course, depth=2)
|
||||
course = get_module(user, request, course.location, student_module_cache, course_id=course_id)
|
||||
course = get_module(user, request, course.location, student_module_cache, course_id)
|
||||
|
||||
chapters = list()
|
||||
for chapter in course.get_display_items():
|
||||
hide_from_toc = chapter.metadata.get('hide_from_toc','false').lower() == 'true'
|
||||
if hide_from_toc:
|
||||
continue
|
||||
continue
|
||||
|
||||
sections = list()
|
||||
for section in chapter.get_display_items():
|
||||
@@ -131,7 +131,7 @@ def get_section(course_module, chapter, section):
|
||||
return section_module
|
||||
|
||||
|
||||
def get_module(user, request, location, student_module_cache, position=None, course_id=None):
|
||||
def get_module(user, request, location, student_module_cache, course_id, position=None):
|
||||
''' Get an instance of the xmodule class identified by location,
|
||||
setting the state based on an existing StudentModule, or creating one if none
|
||||
exists.
|
||||
@@ -141,21 +141,14 @@ def get_module(user, request, location, student_module_cache, position=None, cou
|
||||
- request : current django HTTPrequest
|
||||
- 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
|
||||
- position : extra information from URL for user-specified
|
||||
position within module
|
||||
|
||||
Returns: xmodule instance
|
||||
|
||||
'''
|
||||
descriptor = modulestore().get_item(location)
|
||||
|
||||
# NOTE:
|
||||
# A 'course_id' is understood to be the triplet (org, course, run), for example
|
||||
# (MITx, 6.002x, 2012_Spring).
|
||||
# At the moment generic XModule does not contain enough information to replicate
|
||||
# the triplet (it is missing 'run'), so we must pass down course_id
|
||||
if course_id is None:
|
||||
course_id = descriptor.location.course_id # Will NOT produce (org, course, run) for non-CourseModule's
|
||||
descriptor = modulestore().get_instance(course_id, location)
|
||||
|
||||
# Short circuit--if the user shouldn't have access, bail without doing any work
|
||||
if not has_access(user, descriptor, 'load'):
|
||||
@@ -181,7 +174,7 @@ def get_module(user, request, location, student_module_cache, position=None, cou
|
||||
# Setup system context for module instance
|
||||
ajax_url = reverse('modx_dispatch',
|
||||
kwargs=dict(course_id=course_id,
|
||||
id=descriptor.location.url(),
|
||||
location=descriptor.location.url(),
|
||||
dispatch=''),
|
||||
)
|
||||
|
||||
@@ -208,7 +201,7 @@ def get_module(user, request, location, student_module_cache, position=None, cou
|
||||
Delegate to get_module. It does an access check, so may return None
|
||||
"""
|
||||
return get_module(user, request, location,
|
||||
student_module_cache, position, course_id=course_id)
|
||||
student_module_cache, course_id, position)
|
||||
|
||||
# TODO (cpennington): When modules are shared between courses, the static
|
||||
# prefix is going to have to be specific to the module, not the directory
|
||||
@@ -276,7 +269,7 @@ def get_instance_module(user, module, student_module_cache):
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_shared_instance_module(user, module, student_module_cache):
|
||||
def get_shared_instance_module(course_id, user, module, student_module_cache):
|
||||
"""
|
||||
Return shared_module is a StudentModule specific to all modules with the same
|
||||
'shared_state_key' attribute, or None if the module does not elect to
|
||||
@@ -284,7 +277,7 @@ def get_shared_instance_module(user, module, student_module_cache):
|
||||
"""
|
||||
if user.is_authenticated():
|
||||
# To get the shared_state_key, we need to descriptor
|
||||
descriptor = modulestore().get_item(module.location)
|
||||
descriptor = modulestore().get_instance(course_id, module.location)
|
||||
|
||||
shared_state_key = getattr(module, 'shared_state_key', None)
|
||||
if shared_state_key is not None:
|
||||
@@ -325,8 +318,8 @@ def xqueue_callback(request, course_id, userid, id, dispatch):
|
||||
user = User.objects.get(id=userid)
|
||||
|
||||
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
|
||||
user, modulestore().get_item(id), depth=0, select_for_update=True)
|
||||
instance = get_module(user, request, id, student_module_cache)
|
||||
user, modulestore().get_instance(course_id, id), depth=0, select_for_update=True)
|
||||
instance = get_module(user, request, id, student_module_cache, course_id)
|
||||
if instance is None:
|
||||
log.debug("No module {0} for user {1}--access denied?".format(id, user))
|
||||
raise Http404
|
||||
@@ -362,7 +355,7 @@ def xqueue_callback(request, course_id, userid, id, dispatch):
|
||||
return HttpResponse("")
|
||||
|
||||
|
||||
def modx_dispatch(request, dispatch=None, id=None, course_id=None):
|
||||
def modx_dispatch(request, dispatch, location, course_id):
|
||||
''' Generic view for extensions. This is where AJAX calls go.
|
||||
|
||||
Arguments:
|
||||
@@ -371,7 +364,8 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None):
|
||||
- dispatch -- the command string to pass through to the module's handle_ajax call
|
||||
(e.g. 'problem_reset'). If this string contains '?', only pass
|
||||
through the part before the first '?'.
|
||||
- id -- the module id. Used to look up the XModule instance
|
||||
- location -- the module location. Used to look up the XModule instance
|
||||
- course_id -- defines the course context for this request.
|
||||
'''
|
||||
# ''' (fix emacs broken parsing)
|
||||
|
||||
@@ -380,6 +374,12 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None):
|
||||
if request.FILES:
|
||||
for fileinput_id in request.FILES.keys():
|
||||
inputfiles = request.FILES.getlist(fileinput_id)
|
||||
|
||||
if len(inputfiles) > settings.MAX_FILEUPLOADS_PER_INPUT:
|
||||
too_many_files_msg = 'Submission aborted! Maximum %d files may be submitted at once' %\
|
||||
settings.MAX_FILEUPLOADS_PER_INPUT
|
||||
return HttpResponse(json.dumps({'success': too_many_files_msg}))
|
||||
|
||||
for inputfile in inputfiles:
|
||||
if inputfile.size > settings.STUDENT_FILEUPLOAD_MAX_SIZE: # Bytes
|
||||
file_too_big_msg = 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %\
|
||||
@@ -387,16 +387,18 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None):
|
||||
return HttpResponse(json.dumps({'success': file_too_big_msg}))
|
||||
p[fileinput_id] = inputfiles
|
||||
|
||||
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(request.user, modulestore().get_item(id))
|
||||
instance = get_module(request.user, request, id, student_module_cache, course_id=course_id)
|
||||
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
|
||||
request.user, modulestore().get_instance(course_id, location))
|
||||
|
||||
instance = get_module(request.user, request, location, student_module_cache, course_id)
|
||||
if instance is None:
|
||||
# Either permissions just changed, or someone is trying to be clever
|
||||
# and load something they shouldn't have access to.
|
||||
log.debug("No module {0} for user {1}--access denied?".format(id, user))
|
||||
log.debug("No module {0} for user {1}--access denied?".format(location, user))
|
||||
raise Http404
|
||||
|
||||
instance_module = get_instance_module(request.user, instance, student_module_cache)
|
||||
shared_module = get_shared_instance_module(request.user, instance, student_module_cache)
|
||||
shared_module = get_shared_instance_module(course_id, request.user, instance, student_module_cache)
|
||||
|
||||
# Don't track state for anonymous users (who don't have student modules)
|
||||
if instance_module is not None:
|
||||
|
||||
@@ -303,7 +303,7 @@ class TestViewAuth(PageLoader):
|
||||
'instructor_dashboard',
|
||||
'gradebook',
|
||||
'grade_summary',)]
|
||||
urls.append(reverse('student_profile', kwargs={'course_id': course.id,
|
||||
urls.append(reverse('student_progress', kwargs={'course_id': course.id,
|
||||
'student_id': user(self.student).id}))
|
||||
return urls
|
||||
|
||||
@@ -388,7 +388,7 @@ class TestViewAuth(PageLoader):
|
||||
list of urls that students should be able to see only
|
||||
after launch, but staff should see before
|
||||
"""
|
||||
urls = reverse_urls(['info', 'courseware', 'profile'], course)
|
||||
urls = reverse_urls(['info', 'courseware', 'progress'], course)
|
||||
urls.extend([
|
||||
reverse('book', kwargs={'course_id': course.id, 'book_index': book.title})
|
||||
for book in course.textbooks
|
||||
@@ -411,7 +411,7 @@ 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_profile', kwargs={'course_id': course.id,
|
||||
urls.append(reverse('student_progress', kwargs={'course_id': course.id,
|
||||
'student_id': user(self.student).id}))
|
||||
return urls
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ def courses(request):
|
||||
'''
|
||||
Render "find courses" page. The course selection work is done in courseware.courses.
|
||||
'''
|
||||
universities = get_courses_by_university(request.user,
|
||||
universities = get_courses_by_university(request.user,
|
||||
domain=request.META.get('HTTP_HOST'))
|
||||
return render_to_response("courses.html", {'universities': universities})
|
||||
|
||||
@@ -153,7 +153,7 @@ def index(request, course_id, chapter=None, section=None,
|
||||
section_descriptor)
|
||||
module = get_module(request.user, request,
|
||||
section_descriptor.location,
|
||||
student_module_cache, course_id=course_id)
|
||||
student_module_cache, course_id)
|
||||
if module is None:
|
||||
# User is probably being clever and trying to access something
|
||||
# they don't have access to.
|
||||
@@ -168,7 +168,7 @@ def index(request, course_id, chapter=None, section=None,
|
||||
# Add a list of all the errors...
|
||||
context['course_errors'] = modulestore().get_item_errors(course.location)
|
||||
|
||||
result = render_to_response('courseware.html', context)
|
||||
result = render_to_response('courseware/courseware.html', context)
|
||||
except:
|
||||
# In production, don't want to let a 500 out for any reason
|
||||
if settings.DEBUG:
|
||||
@@ -184,8 +184,9 @@ def index(request, course_id, chapter=None, section=None,
|
||||
position=position
|
||||
))
|
||||
try:
|
||||
result = render_to_response('courseware-error.html',
|
||||
{'staff_access': staff_access})
|
||||
result = render_to_response('courseware/courseware-error.html',
|
||||
{'staff_access': staff_access,
|
||||
'course' : course})
|
||||
except:
|
||||
result = HttpResponse("There was an unrecoverable error")
|
||||
|
||||
@@ -229,7 +230,7 @@ def course_info(request, course_id):
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
staff_access = has_access(request.user, course, 'staff')
|
||||
|
||||
return render_to_response('info.html', {'course': course,
|
||||
return render_to_response('courseware/info.html', {'course': course,
|
||||
'staff_access': staff_access,})
|
||||
|
||||
|
||||
@@ -292,11 +293,10 @@ def news(request, course_id):
|
||||
|
||||
@login_required
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
def profile(request, course_id, student_id=None):
|
||||
""" User profile. Show username, location, etc, as well as grades .
|
||||
We need to allow the user to change some of these settings.
|
||||
def progress(request, course_id, student_id=None):
|
||||
""" User progress. We show the grade bar and every problem score.
|
||||
|
||||
Course staff are allowed to see the profiles of students in their class.
|
||||
Course staff are allowed to see the progress of students in their class.
|
||||
"""
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
staff_access = has_access(request.user, course, 'staff')
|
||||
@@ -310,28 +310,22 @@ def profile(request, course_id, student_id=None):
|
||||
raise Http404
|
||||
student = User.objects.get(id=int(student_id))
|
||||
|
||||
user_info = UserProfile.objects.get(user=student)
|
||||
|
||||
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(request.user, course)
|
||||
course_module = get_module(request.user, request, course.location, student_module_cache, course_id=course_id)
|
||||
course_module = get_module(request.user, request, course.location,
|
||||
student_module_cache, course_id)
|
||||
|
||||
courseware_summary = grades.progress_summary(student, course_module, course.grader, student_module_cache)
|
||||
courseware_summary = grades.progress_summary(student, course_module,
|
||||
course.grader, student_module_cache)
|
||||
grade_summary = grades.grade(request.user, request, course, student_module_cache)
|
||||
|
||||
context = {'name': user_info.name,
|
||||
'username': student.username,
|
||||
'location': user_info.location,
|
||||
'language': user_info.language,
|
||||
'email': student.email,
|
||||
'course': course,
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
context = {'course': course,
|
||||
'courseware_summary': courseware_summary,
|
||||
'grade_summary': grade_summary,
|
||||
'staff_access': staff_access,
|
||||
}
|
||||
context.update()
|
||||
|
||||
return render_to_response('profile.html', context)
|
||||
return render_to_response('courseware/progress.html', context)
|
||||
|
||||
|
||||
|
||||
@@ -359,7 +353,7 @@ def gradebook(request, course_id):
|
||||
}
|
||||
for student in enrolled_students]
|
||||
|
||||
return render_to_response('gradebook.html', {'students': student_info,
|
||||
return render_to_response('courseware/gradebook.html', {'students': student_info,
|
||||
'course': course,
|
||||
'course_id': course_id,
|
||||
# Checked above
|
||||
@@ -374,7 +368,7 @@ def grade_summary(request, course_id):
|
||||
# For now, just a static page
|
||||
context = {'course': course,
|
||||
'staff_access': True,}
|
||||
return render_to_response('grade_summary.html', context)
|
||||
return render_to_response('courseware/grade_summary.html', context)
|
||||
|
||||
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@@ -385,4 +379,4 @@ def instructor_dashboard(request, course_id):
|
||||
# For now, just a static page
|
||||
context = {'course': course,
|
||||
'staff_access': True,}
|
||||
return render_to_response('instructor_dashboard.html', context)
|
||||
return render_to_response('courseware/instructor_dashboard.html', context)
|
||||
|
||||
@@ -3,13 +3,16 @@ from django_comment_client.models import Permission, Role
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = ''
|
||||
args = 'course_id'
|
||||
help = 'Seed default permisssions and roles'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
administrator_role = Role.objects.get_or_create(name="Administrator", course_id="MITx/6.002x/2012_Fall")[0]
|
||||
moderator_role = Role.objects.get_or_create(name="Moderator", course_id="MITx/6.002x/2012_Fall")[0]
|
||||
student_role = Role.objects.get_or_create(name="Student", course_id="MITx/6.002x/2012_Fall")[0]
|
||||
if len(args) != 1:
|
||||
raise CommandError("The number of arguments does not match. ")
|
||||
course_id = args[0]
|
||||
administrator_role = Role.objects.get_or_create(name="Administrator", course_id=course_id)[0]
|
||||
moderator_role = Role.objects.get_or_create(name="Moderator", course_id=course_id)[0]
|
||||
student_role = Role.objects.get_or_create(name="Student", course_id=course_id)[0]
|
||||
|
||||
for per in ["vote", "update_thread", "follow_thread", "unfollow_thread",
|
||||
"update_comment", "create_sub_comment", "unvote" , "create_thread",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
|
||||
@@ -14,7 +15,7 @@ def index(request, course_id, book_index, page=0):
|
||||
table_of_contents = textbook.table_of_contents
|
||||
|
||||
return render_to_response('staticbook.html',
|
||||
{'page': int(page), 'course': course,
|
||||
{'page': int(page), 'course': course, 'book_url': textbook.book_url,
|
||||
'table_of_contents': table_of_contents,
|
||||
'staff_access': staff_access})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user