Merge branch 'feature/bk_forum_int'

This commit is contained in:
Rocky Duan
2012-08-21 12:56:56 -07:00
67 changed files with 549 additions and 562 deletions

View File

@@ -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]

View File

@@ -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.")

View File

@@ -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.

View File

@@ -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"
"""

View File

@@ -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)

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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",

View File

@@ -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})