From 2b23c403450b703842c6cca630a613ba89938722 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 17 Jan 2013 15:25:35 -0500 Subject: [PATCH 001/444] minor tweaks to dropdown text and addition of dropdown menu (without data links) for intructor visilibility dropdown in main discussion forum; --- lms/static/sass/_discussion.scss | 22 ++++++++++++++++++- .../discussion/_filter_dropdown.html | 2 +- lms/templates/discussion/_new_post.html | 2 +- lms/templates/discussion/_single_thread.html | 1 + .../discussion/_thread_list_template.html | 9 +++++++- 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index 809c968fe6..9148421e4c 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -181,7 +181,7 @@ body.discussion { .drop-arrow { float: right; color: #999; - line-height: 36px; + line-height: 37px; } } @@ -1020,6 +1020,18 @@ body.discussion { } } + .group-filter-label { + width: 40px; + margin-left:10px; + } + + .group-filter-select { + margin: 5px 0px 5px 5px; + width: 80px; + font-size:10px; + background: transparent; + border-color: #ccc; + } } .post-list-wrapper { @@ -1327,6 +1339,8 @@ body.discussion { margin-left: 40px; } + + .post-tools { @include clearfix; margin-top: 15px; @@ -1357,6 +1371,12 @@ body.discussion { margin-bottom: 20px; } + .group-visibility-label { + font-size: 12px; + color:#ddd; + font-style: italic; + } + .responses { list-style: none; margin-top: 40px; diff --git a/lms/templates/discussion/_filter_dropdown.html b/lms/templates/discussion/_filter_dropdown.html index 484ee05101..8272fdd062 100644 --- a/lms/templates/discussion/_filter_dropdown.html +++ b/lms/templates/discussion/_filter_dropdown.html @@ -30,7 +30,7 @@ + Show: + +
diff --git a/lms/templates/discussion/_thread_list_template.html b/lms/templates/discussion/_thread_list_template.html index 48aef4debf..31943ef8ad 100644 --- a/lms/templates/discussion/_thread_list_template.html +++ b/lms/templates/discussion/_thread_list_template.html @@ -24,7 +24,6 @@ -
From 5bfd2c33b0ce96066a5b26befe57a7666e7776d0 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 29 Jan 2013 17:34:52 -0500 Subject: [PATCH 004/444] send cohort to post view context --- lms/djangoapps/django_comment_client/forum/views.py | 3 ++- lms/templates/discussion/_new_post.html | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index 29000a225f..9737d59537 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -11,7 +11,7 @@ from django.contrib.auth.models import User from mitxmako.shortcuts import render_to_response, render_to_string from courseware.courses import get_course_with_access -from course_groups.cohorts import get_cohort_id +from course_groups.cohorts import get_cohort_id, get_course_cohorts from courseware.access import has_access from urllib import urlencode @@ -166,6 +166,7 @@ def forum_form_discussion(request, course_id): 'category_map': category_map, 'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict), 'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id), + 'cohorts': get_course_cohorts } # print "start rendering.." return render_to_response('discussion/index.html', context) diff --git a/lms/templates/discussion/_new_post.html b/lms/templates/discussion/_new_post.html index 26c886c6ac..c733704d09 100644 --- a/lms/templates/discussion/_new_post.html +++ b/lms/templates/discussion/_new_post.html @@ -45,13 +45,15 @@ %elif course.metadata.get("allow_anonymous_to_peers", False): %endif - % if true: + % if is_moderator:
Make visible to:
%endif From 1fc26de945e97b736e4e837b8ada57a5ba661114 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 30 Jan 2013 15:19:43 -0500 Subject: [PATCH 005/444] merge master --- common/djangoapps/course_groups/cohorts.py | 3 ++- lms/djangoapps/django_comment_client/forum/views.py | 9 +++++++-- lms/templates/discussion/_new_post.html | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/common/djangoapps/course_groups/cohorts.py b/common/djangoapps/course_groups/cohorts.py index f84e18b214..5c8a1ca067 100644 --- a/common/djangoapps/course_groups/cohorts.py +++ b/common/djangoapps/course_groups/cohorts.py @@ -21,7 +21,8 @@ def is_course_cohorted(course_id): Raises: Http404 if the course doesn't exist. """ - return courses.get_course_by_id(course_id).is_cohorted + #return courses.get_course_by_id(course_id).is_cohorted + return True def get_cohort_id(user, course_id): diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index 9737d59537..715cb575d4 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -117,6 +117,8 @@ def forum_form_discussion(request, course_id): Renders the main Discussion page, potentially filtered by a search query """ course = get_course_with_access(request.user, course_id, 'load') + print "\n\n\n\n\n****************************" + print course category_map = utils.get_discussion_category_map(course) try: @@ -165,10 +167,13 @@ def forum_form_discussion(request, course_id): 'course_id': course.id, 'category_map': category_map, 'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict), - 'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id), - 'cohorts': get_course_cohorts + #'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id), + 'is_moderator': True, + 'cohorts': get_course_cohorts(course_id) } # print "start rendering.." + print "\n\n\n\n\n\n*************************************" + print context return render_to_response('discussion/index.html', context) @login_required diff --git a/lms/templates/discussion/_new_post.html b/lms/templates/discussion/_new_post.html index c733704d09..3af63f9ea1 100644 --- a/lms/templates/discussion/_new_post.html +++ b/lms/templates/discussion/_new_post.html @@ -45,7 +45,7 @@ %elif course.metadata.get("allow_anonymous_to_peers", False): %endif - % if is_moderator: + %if is_moderator:
Make visible to: + +
+ Hints +
+
Suffixes:
+
%kMGTcmunp
+
Operations:
+
^ * / + - ()
+
Functions:
+
sin, cos, tan, sqrt, log10, log2, ln, arccos, arcsin, arctan, abs
+
Constants
+
e, pi
+ + +
+
+
+ + + + +
+ +% endif diff --git a/lms/templates/main_nonav.html b/lms/templates/main_nonav.html new file mode 100644 index 0000000000..f2b87ef348 --- /dev/null +++ b/lms/templates/main_nonav.html @@ -0,0 +1,46 @@ +<%namespace name='static' file='static_content.html'/> + + + + <%block name="title">edX + + + + + <%static:css group='application'/> + + <%static:js group='main_vendor'/> + <%block name="headextra"/> + + + + + + + + % if not course: + <%include file="google_analytics.html" /> + % endif + + + + + + +
+ ${self.body()} + <%block name="bodyextra"/> +
+ + + + <%static:js group='application'/> + <%static:js group='module-js'/> + + <%block name="js_extra"/> + + diff --git a/lms/urls.py b/lms/urls.py index f92b63aac2..2c5db07d00 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -217,6 +217,10 @@ if settings.COURSEWARE_ENABLED: url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/about$', 'courseware.views.course_about', name="about_course"), + # testcenter exam: + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/testcenter_exam/(?P[^/]*)/(?P
[^/]*)/$', + 'courseware.views.testcenter_exam', name="testcenter_exam"), + #Inside the course url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/$', 'courseware.views.course_info', name="course_root"), From 309ac7aa5c1f217f0f5b85cbbbeba0938e4e0268 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 31 Jan 2013 13:27:46 -0500 Subject: [PATCH 007/444] Refactor rubric CSS into a single file. --- .../css/combinedopenended/display.scss | 41 --------------- lms/static/sass/course.scss | 1 + lms/static/sass/course/_rubric.scss | 52 +++++++++++++++++++ lms/static/sass/course/_staff_grading.scss | 47 +---------------- 4 files changed, 54 insertions(+), 87 deletions(-) create mode 100644 lms/static/sass/course/_rubric.scss diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index a4045c9dad..1917471879 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -231,47 +231,6 @@ div.result-container { } } -div.result-container, section.open-ended-child { - .rubric { - margin-bottom:25px; - tr { - margin:10px 0px; - height: 100%; - } - td { - padding: 20px 0px 25px 0px; - margin: 10px 0px; - height: 100%; - } - th { - padding: 5px; - margin: 5px; - } - label, - .view-only { - margin:2px; - position: relative; - padding: 10px 15px 25px 15px; - width: 145px; - height:100%; - display: inline-block; - min-height: 50px; - min-width: 50px; - background-color: #CCC; - font-size: .85em; - } - .grade { - position: absolute; - bottom:0px; - right:0px; - margin:10px; - } - .selected-grade { - background: #666; - color: white; - } - } -} section.open-ended-child { @media print { diff --git a/lms/static/sass/course.scss b/lms/static/sass/course.scss index e900e589b2..d5f620be82 100644 --- a/lms/static/sass/course.scss +++ b/lms/static/sass/course.scss @@ -44,6 +44,7 @@ @import "course/gradebook"; @import "course/tabs"; @import "course/staff_grading"; +@import "course/rubric"; // instructor @import "course/instructor/instructor"; diff --git a/lms/static/sass/course/_rubric.scss b/lms/static/sass/course/_rubric.scss new file mode 100644 index 0000000000..c82d929fac --- /dev/null +++ b/lms/static/sass/course/_rubric.scss @@ -0,0 +1,52 @@ +.rubric { + padding: 40px 0px; + tr { + margin:10px 0px; + height: 100%; + } + td { + padding: 20px 0px 25px 0px; + height: 100%; + border: 1px black solid; + } + th { + padding: 5px; + margin: 5px; + text-align: center; + } + .points-header th { + padding: 0px; + } + label, + .view-only { + margin:2px; + position: relative; + padding: 15px 15px 25px 15px; + width: 130px; + height:100%; + min-height: 50px; + min-width: 50px; + font-size: .9em; + background-color: white; + display: block; + } + .grade { + position: absolute; + bottom:0px; + right:0px; + margin:10px; + } + .selected-grade { + background: #666; + color: white; + } + input[type=radio]:checked + label { + background: #666; + color: white; } + input[class='score-selection'] { + position: relative; + margin-left: 10px; + font-size: 16px; + } +} + diff --git a/lms/static/sass/course/_staff_grading.scss b/lms/static/sass/course/_staff_grading.scss index 177bd9e5e2..4d4da484de 100644 --- a/lms/static/sass/course/_staff_grading.scss +++ b/lms/static/sass/course/_staff_grading.scss @@ -12,7 +12,7 @@ div.peer-grading{ label { margin: 10px; padding: 5px; - display: inline-block; + @include inline-block; min-width: 50px; background-color: #CCC; text-size: 1.5em; @@ -176,49 +176,4 @@ div.peer-grading{ } } padding: 40px; - .rubric { - tr { - margin:10px 0px; - height: 100%; - } - td { - padding: 20px 0px 25px 0px; - height: 100%; - } - th { - padding: 5px; - margin: 5px; - } - label, - .view-only { - margin:2px; - position: relative; - padding: 15px 15px 25px 15px; - width: 150px; - height:100%; - display: inline-block; - min-height: 50px; - min-width: 50px; - background-color: #CCC; - font-size: .9em; - } - .grade { - position: absolute; - bottom:0px; - right:0px; - margin:10px; - } - .selected-grade { - background: #666; - color: white; - } - input[type=radio]:checked + label { - background: #666; - color: white; } - input[class='score-selection'] { - display: none; - } - } - } - From 683976d7adf4eebedd84894d79b13572072f8dfb Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 31 Jan 2013 13:28:19 -0500 Subject: [PATCH 008/444] Add scores to the top of the rubric, remove from individual cells --- common/lib/xmodule/xmodule/combined_open_ended_rubric.py | 5 ++++- lms/templates/open_ended_rubric.html | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py index 4380e32d5b..50ec22f033 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py @@ -25,10 +25,13 @@ class CombinedOpenEndedRubric(object): ''' try: rubric_categories = self.extract_categories(rubric_xml) + max_scores = map((lambda cat: cat['options'][-1]['points']), rubric_categories) + max_score = max(max_scores) html = self.system.render_template('open_ended_rubric.html', {'categories' : rubric_categories, 'has_score': self.has_score, - 'view_only': self.view_only}) + 'view_only': self.view_only, + 'max_score': max_score}) except: raise RubricParsingError("[render_rubric] Could not parse the rubric with xml: {0}".format(rubric_xml)) return html diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html index 8d40c7d2b8..b92ad04bde 100644 --- a/lms/templates/open_ended_rubric.html +++ b/lms/templates/open_ended_rubric.html @@ -8,6 +8,14 @@

Select the criteria you feel best represents this submission in each category.

% endif + + + % for i in range(max_score + 1): + + % endfor + % for i in range(len(categories)): <% category = categories[i] %> @@ -23,7 +31,6 @@
% endif ${option['text']} -
[${option['points']} points]
% else: From 2e0f90081eb12c822b603861cbf86e8e34601866 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 31 Jan 2013 14:23:17 -0500 Subject: [PATCH 009/444] Make rubric cleaner and visually simpler --- lms/static/sass/course/_rubric.scss | 17 ++++++++--------- lms/templates/open_ended_rubric.html | 6 +++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lms/static/sass/course/_rubric.scss b/lms/static/sass/course/_rubric.scss index c82d929fac..9aa0ca2f2a 100644 --- a/lms/static/sass/course/_rubric.scss +++ b/lms/static/sass/course/_rubric.scss @@ -1,5 +1,5 @@ .rubric { - padding: 40px 0px; + margin: 40px 0px; tr { margin:10px 0px; height: 100%; @@ -8,6 +8,7 @@ padding: 20px 0px 25px 0px; height: 100%; border: 1px black solid; + text-align: center; } th { padding: 5px; @@ -17,13 +18,11 @@ .points-header th { padding: 0px; } - label, - .view-only { - margin:2px; + .rubric-label + { position: relative; - padding: 15px 15px 25px 15px; + padding: 15px 15px 25px; width: 130px; - height:100%; min-height: 50px; min-width: 50px; font-size: .9em; @@ -40,9 +39,9 @@ background: #666; color: white; } - input[type=radio]:checked + label { - background: #666; - color: white; } + input[type=radio]:checked + .rubric-label { + background: white; + color: $base-font-color; } input[class='score-selection'] { position: relative; margin-left: 10px; diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html index b92ad04bde..a2d8d6945c 100644 --- a/lms/templates/open_ended_rubric.html +++ b/lms/templates/open_ended_rubric.html @@ -26,15 +26,15 @@ % if view_only: ## if this is the selected rubric block, show it highlighted % if option['selected']: -
+
% else: -
+
% endif ${option['text']}
% else: - + % endif % endfor From 3fa67c14255887cd49c5e5002eef8c0fb46085b7 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 31 Jan 2013 17:05:15 -0500 Subject: [PATCH 010/444] allow moderators and students to select cohort on post creation --- common/djangoapps/course_groups/cohorts.py | 19 +++++++++++++++++ .../src/discussion/views/new_post_view.coffee | 10 ++++++++- .../django_comment_client/base/views.py | 17 ++++++++++++--- .../django_comment_client/forum/views.py | 15 ++++++------- lms/templates/discussion/_new_post.html | 21 ++++++++++++------- 5 files changed, 64 insertions(+), 18 deletions(-) diff --git a/common/djangoapps/course_groups/cohorts.py b/common/djangoapps/course_groups/cohorts.py index 5c8a1ca067..4fda68bf36 100644 --- a/common/djangoapps/course_groups/cohorts.py +++ b/common/djangoapps/course_groups/cohorts.py @@ -30,6 +30,9 @@ def get_cohort_id(user, course_id): Given a course id and a user, return the id of the cohort that user is assigned to in that course. If they don't have a cohort, return None. """ + print "\n\n\n\n\n*********************************" + print user + print course_id cohort = get_cohort(user, course_id) return None if cohort is None else cohort.id @@ -64,7 +67,23 @@ def is_commentable_cohorted(course_id, commentable_id): ans)) return ans + +def get_cohorted_commentables(course_id): + """ + Given a course_id return a list of strings representing cohorted commentables + """ + course = courses.get_course_by_id(course_id) + + if not course.is_cohorted: + # this is the easy case :) + ans = [] + else: + ans = course.top_level_discussion_topic_ids + + return ans + + def get_cohort(user, course_id): """ Given a django User and a course_id, return the user's cohort in that diff --git a/common/static/coffee/src/discussion/views/new_post_view.coffee b/common/static/coffee/src/discussion/views/new_post_view.coffee index 1c49fdbc8e..be146587df 100644 --- a/common/static/coffee/src/discussion/views/new_post_view.coffee +++ b/common/static/coffee/src/discussion/views/new_post_view.coffee @@ -14,8 +14,9 @@ if Backbone? @setSelectedTopic() DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body" + @$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions() - + events: "submit .new-post-form": "createPost" "click .topic_dropdown_button": "toggleTopicDropdown" @@ -65,6 +66,11 @@ if Backbone? @topicText = @getFullTopicName($target) @topicId = $target.data('discussion_id') @setSelectedTopic() + if $target.attr('cohorted') == "True" + $('.choose-cohort').show(); + else + $('.choose-cohort').hide(); + setSelectedTopic: -> @dropdownButton.html(@fitName(@topicText) + ' ') @@ -116,6 +122,7 @@ if Backbone? title = @$(".new-post-title").val() body = @$(".new-post-body").find(".wmd-input").val() tags = @$(".new-post-tags").val() + group = @$(".new-post-group option:selected").attr("value") anonymous = false || @$("input.discussion-anonymous").is(":checked") anonymous_to_peers = false || @$("input.discussion-anonymous-to-peers").is(":checked") @@ -137,6 +144,7 @@ if Backbone? anonymous: anonymous anonymous_to_peers: anonymous_to_peers auto_subscribe: follow + group_id: group error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors")) success: (response, textStatus) => # TODO: Move this out of the callback, this makes it feel sluggish diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index 777c7bafce..6e86e629a1 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -89,20 +89,31 @@ def create_thread(request, course_id, commentable_id): 'user_id' : request.user.id, }) + + user = cc.User.from_django_user(request.user) # Cohort the thread if the commentable is cohorted. if is_commentable_cohorted(course_id, commentable_id): - user_group_id = get_cohort_id(request.user, course_id) + print "********************** IS COHORTED" + user_group_id = get_cohort_id(user, course_id) + print "********************** USER GOUP ID IS" + print user_group_id # TODO (vshnayder): once we have more than just cohorts, we'll want to # change this to a single get_group_for_user_and_commentable function # that can do different things depending on the commentable_id - if cached_has_permission(request.user, "see_all_cohorts", course_id): + if cached_has_permission(request.user, "see_all_cohorts", course_id) or True: # admins can optionally choose what group to post as + + print "********************** CACHED HAS PERMISSIONS TRUE" group_id = post.get('group_id', user_group_id) else: # regular users always post with their own id. + print "********************** CACHED HAS PERMISSIONS FALSE" group_id = user_group_id - + print "\n\n\n\n\n********************************* group is " + print group_id + print "\n\n\n\n\n********************************* and post is" + print post thread.update_attributes(group_id=group_id) thread.save() diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index 715cb575d4..b5c65202d9 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -11,7 +11,7 @@ from django.contrib.auth.models import User from mitxmako.shortcuts import render_to_response, render_to_string from courseware.courses import get_course_with_access -from course_groups.cohorts import get_cohort_id, get_course_cohorts +from course_groups.cohorts import get_cohort_id, get_course_cohorts, get_cohorted_commentables, is_course_cohorted from courseware.access import has_access from urllib import urlencode @@ -128,7 +128,8 @@ def forum_form_discussion(request, course_id): log.error("Error loading forum discussion threads: %s" % str(err)) raise Http404 - user_info = cc.User.from_django_user(request.user).to_dict() + user = cc.User.from_django_user(request.user) + user_info = user.to_dict() annotated_content_info = utils.get_metadata_for_threads(course_id, threads, request.user, user_info) @@ -167,13 +168,13 @@ def forum_form_discussion(request, course_id): 'course_id': course.id, 'category_map': category_map, 'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict), - #'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id), - 'is_moderator': True, - 'cohorts': get_course_cohorts(course_id) + 'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id), + 'cohorts': get_course_cohorts(course_id), + 'cohort': get_cohort_id(user, course_id), + 'cohorted_commentables': get_cohorted_commentables(course_id), + 'is_course_cohorted': is_course_cohorted(course_id) } # print "start rendering.." - print "\n\n\n\n\n\n*************************************" - print context return render_to_response('discussion/index.html', context) @login_required diff --git a/lms/templates/discussion/_new_post.html b/lms/templates/discussion/_new_post.html index 3af63f9ea1..1bc6148dbe 100644 --- a/lms/templates/discussion/_new_post.html +++ b/lms/templates/discussion/_new_post.html @@ -9,7 +9,7 @@ <%def name="render_entry(entries, entry)"> -
  • ${entry}
  • +
  • ${entry}
  • <%def name="render_category(categories, category)"> @@ -21,6 +21,9 @@ + + +
    @@ -45,14 +48,18 @@ %elif course.metadata.get("allow_anonymous_to_peers", False): %endif - %if is_moderator: -
    + %if is_course_cohorted: +
    Make visible to: - - %for c in cohorts: - - %endfor + %if is_moderator: + %for c in cohorts: + + %endfor + %else: + + %endif
    From c25cd33214c8232250bd823121e7c53d408c4595 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 31 Jan 2013 17:22:54 -0500 Subject: [PATCH 011/444] remove print --- lms/djangoapps/django_comment_client/forum/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index b5c65202d9..64ba703d55 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -117,8 +117,6 @@ def forum_form_discussion(request, course_id): Renders the main Discussion page, potentially filtered by a search query """ course = get_course_with_access(request.user, course_id, 'load') - print "\n\n\n\n\n****************************" - print course category_map = utils.get_discussion_category_map(course) try: From f2f745965655e1d19c853fded737d4c8cb6b3064 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 31 Jan 2013 17:24:08 -0500 Subject: [PATCH 012/444] remove more prints --- lms/djangoapps/django_comment_client/base/views.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index 6e86e629a1..7f0809cabb 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -94,26 +94,16 @@ def create_thread(request, course_id, commentable_id): # Cohort the thread if the commentable is cohorted. if is_commentable_cohorted(course_id, commentable_id): - print "********************** IS COHORTED" user_group_id = get_cohort_id(user, course_id) - print "********************** USER GOUP ID IS" - print user_group_id # TODO (vshnayder): once we have more than just cohorts, we'll want to # change this to a single get_group_for_user_and_commentable function # that can do different things depending on the commentable_id if cached_has_permission(request.user, "see_all_cohorts", course_id) or True: # admins can optionally choose what group to post as - - print "********************** CACHED HAS PERMISSIONS TRUE" group_id = post.get('group_id', user_group_id) else: # regular users always post with their own id. - print "********************** CACHED HAS PERMISSIONS FALSE" group_id = user_group_id - print "\n\n\n\n\n********************************* group is " - print group_id - print "\n\n\n\n\n********************************* and post is" - print post thread.update_attributes(group_id=group_id) thread.save() From da1660a043fcf6e28769fd3a9b8c26f55fdd98d2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 31 Jan 2013 17:34:37 -0500 Subject: [PATCH 013/444] fix get_cohorted_commentables --- common/djangoapps/course_groups/cohorts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/djangoapps/course_groups/cohorts.py b/common/djangoapps/course_groups/cohorts.py index 4fda68bf36..4d74f70b26 100644 --- a/common/djangoapps/course_groups/cohorts.py +++ b/common/djangoapps/course_groups/cohorts.py @@ -79,7 +79,7 @@ def get_cohorted_commentables(course_id): # this is the easy case :) ans = [] else: - ans = course.top_level_discussion_topic_ids + ans = course.cohorted_discussions return ans From 07638440ac93d63893ea2d0b3a67f799bfeb1596 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Thu, 31 Jan 2013 18:33:22 -0500 Subject: [PATCH 014/444] rename testcenter_exam to timed_exam, and read duration from metadata (policy.json) --- lms/djangoapps/courseware/views.py | 17 ++++++++++++----- lms/templates/courseware/testcenter_exam.html | 3 ++- lms/urls.py | 6 +++--- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index c7838156cb..1ac7cebd4b 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -301,7 +301,7 @@ def index(request, course_id, chapter=None, section=None, @login_required @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) -def testcenter_exam(request, course_id, chapter, section): +def timed_exam(request, course_id, chapter, section): """ Displays only associated content. If course, chapter, and section are all specified, renders the page, or returns an error if they @@ -387,20 +387,27 @@ def testcenter_exam(request, course_id, chapter, section): # they don't have access to. raise Http404 - # Save where we are in the chapter + # Save where we are in the chapter NOT! # instance_module = get_instance_module(course_id, request.user, chapter_module, student_module_cache) # save_child_position(chapter_module, section, instance_module) context['content'] = section_module.get_html() - # figure out when the exam should end. Going forward, this is determined by getting a "normal" + # figure out when the timed exam should end. Going forward, this is determined by getting a "normal" # duration from the test, then doing some math to modify the duration based on accommodations, # and then use that value as the end. Once we have calculated this, it should be sticky -- we # use the same value for future requests, unless it's a tester. + + # get value for duration from the section's metadata: + if 'duration' not in section_descriptor.metadata: + raise Http404 + + # for now, assume that the duration is set as an integer value, indicating the number of seconds: + duration = int(section_descriptor.metadata.get('duration')) - # Let's try 600s for now... - context['end_date'] = (time() + 600) * 1000 + # This value should be UTC time as number of milliseconds since epoch. + context['end_date'] = (time() + duration) * 1000 result = render_to_response('courseware/testcenter_exam.html', context) except Exception as e: diff --git a/lms/templates/courseware/testcenter_exam.html b/lms/templates/courseware/testcenter_exam.html index 66adfefcff..638778f7cd 100644 --- a/lms/templates/courseware/testcenter_exam.html +++ b/lms/templates/courseware/testcenter_exam.html @@ -67,7 +67,8 @@ return ( num < 10 ? "0" : "" ) + num; } - // set the end time when the template is rendered + // set the end time when the template is rendered. + // This value should be UTC time as number of milliseconds since epoch. var endTime = new Date(${end_date}); var currentTime = new Date(); var remaining_secs = Math.floor((endTime - currentTime)/1000); diff --git a/lms/urls.py b/lms/urls.py index 2c5db07d00..021079333a 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -217,9 +217,9 @@ if settings.COURSEWARE_ENABLED: url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/about$', 'courseware.views.course_about', name="about_course"), - # testcenter exam: - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/testcenter_exam/(?P[^/]*)/(?P
    [^/]*)/$', - 'courseware.views.testcenter_exam', name="testcenter_exam"), + # timed exam: + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/timed_exam/(?P[^/]*)/(?P
    [^/]*)/$', + 'courseware.views.timed_exam', name="timed_exam"), #Inside the course url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/$', From 6575386d6992bea4e68332370d0e9de13180c861 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 08:43:55 -0500 Subject: [PATCH 015/444] Refactor rubric JS so that we don't have to keep duplicating this code. --- .../js/src/combinedopenended/display.coffee | 29 +++++++++++++++ .../peer_grading/peer_grading_problem.coffee | 15 ++------ .../src/staff_grading/staff_grading.coffee | 37 ++++--------------- 3 files changed, 41 insertions(+), 40 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 594efe2f9b..c4560559c8 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -1,3 +1,32 @@ +class @Rubric + constructor: () -> + + # finds the scores for each rubric category + @get_score_list: () => + # find the number of categories: + num_categories = $('table.rubric tr').length + + score_lst = [] + # get the score for each one + for i in [0..(num_categories-2)] + score = $("input[name='score-selection-#{i}']:checked").val() + score_lst.push(score) + + return score_lst + + @get_total_score: () -> + score_lst = @get_score_list() + + @check_complete: () -> + # check to see whether or not any categories have not been scored + num_categories = $('table.rubric tr').length + # -2 because we want to skip the header + for i in [0..(num_categories-2)] + score = $("input[name='score-selection-#{i}']:checked").val() + if score == undefined + return false + return true + class @CombinedOpenEnded constructor: (element) -> @element=element diff --git a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee index c4b87eb30e..525891bb03 100644 --- a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee +++ b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee @@ -239,7 +239,7 @@ class PeerGradingProblem score_lst = [] # get the score for each one - for i in [0..(num_categories-1)] + for i in [0..(num_categories-2)] score = $("input[name='score-selection-#{i}']:checked").val() score_lst.push(score) @@ -315,17 +315,10 @@ class PeerGradingProblem # called after a grade is selected on the interface graded_callback: (event) => - @grade = $("input[name='grade-selection']:checked").val() - if @grade == undefined - return # check to see whether or not any categories have not been scored - num_categories = $('table.rubric tr').length - for i in [0..(num_categories-1)] - score = $("input[name='score-selection-#{i}']:checked").val() - if score == undefined - return - # show button if we have scores for all categories - @show_submit_button() + if Rubric.check_complete(): + # show button if we have scores for all categories + @show_submit_button() diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 005a8e682e..2d3cafd3e7 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -232,35 +232,14 @@ class @StaffGrading graded_callback: () => - @grade = $("input[name='grade-selection']:checked").val() - if @grade == undefined - return - # check to see whether or not any categories have not been scored - num_categories = $('table.rubric tr').length - for i in [0..(num_categories-1)] - score = $("input[name='score-selection-#{i}']:checked").val() - if score == undefined - return - # show button if we have scores for all categories - @state = state_graded - @submit_button.show() + # show button if we have scores for all categories + if Rubric.check_complete() + @state = state_graded + @submit_button.show() set_button_text: (text) => @action_button.attr('value', text) - # finds the scores for each rubric category - get_score_list: () => - # find the number of categories: - num_categories = $('table.rubric tr').length - - score_lst = [] - # get the score for each one - for i in [0..(num_categories-1)] - score = $("input[name='score-selection-#{i}']:checked").val() - score_lst.push(score) - - return score_lst - ajax_callback: (response) => # always clear out errors and messages on transition. @error_msg = '' @@ -285,8 +264,8 @@ class @StaffGrading skip_and_get_next: () => data = - score: @grade - rubric_scores: @get_score_list() + score: Rubric.get_total_score() + rubric_scores: Rubric.get_score_list() feedback: @feedback_area.val() submission_id: @submission_id location: @location @@ -299,8 +278,8 @@ class @StaffGrading submit_and_get_next: () -> data = - score: @grade - rubric_scores: @get_score_list() + score: Rubric.get_total_score() + rubric_scores: Rubric.get_score_list() feedback: @feedback_area.val() submission_id: @submission_id location: @location From f05bda764470b80873937cb8573cf3db27228137 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 08:44:08 -0500 Subject: [PATCH 016/444] Visual updates to rubric --- lms/static/sass/course/_rubric.scss | 3 ++- lms/templates/open_ended_rubric.html | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lms/static/sass/course/_rubric.scss b/lms/static/sass/course/_rubric.scss index 9aa0ca2f2a..722a790e6d 100644 --- a/lms/static/sass/course/_rubric.scss +++ b/lms/static/sass/course/_rubric.scss @@ -35,7 +35,8 @@ right:0px; margin:10px; } - .selected-grade { + .selected-grade, + .selected-grade .rubric-label { background: #666; color: white; } diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html index a2d8d6945c..eb3fc564b4 100644 --- a/lms/templates/open_ended_rubric.html +++ b/lms/templates/open_ended_rubric.html @@ -22,14 +22,14 @@
    % for j in range(len(category['options'])): <% option = category['options'][j] %> + %if option['selected']: +
    + ${i} points +
    ${category['description']} + %else: + % endif % if view_only: ## if this is the selected rubric block, show it highlighted - % if option['selected']: -
    - % else:
    - % endif ${option['text']}
    % else: From 0ec3be18155d5136aeade3139686f04c07b6d8a4 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 10:17:16 -0500 Subject: [PATCH 017/444] Calculate the total score from the rubric. --- .../xmodule/xmodule/js/src/combinedopenended/display.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index c4560559c8..576fb7290d 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -16,6 +16,10 @@ class @Rubric @get_total_score: () -> score_lst = @get_score_list() + tot = 0 + for score in score_lst + tot += parseInt(score) + return tot @check_complete: () -> # check to see whether or not any categories have not been scored From cb44918f4958ebef6cd70bb8d1e760c534f046ea Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 10:31:46 -0500 Subject: [PATCH 018/444] Remove duplicate Javascript and remove total grade selection. --- .../peer_grading/peer_grading_problem.coffee | 35 ++----------------- .../src/staff_grading/staff_grading.coffee | 15 -------- 2 files changed, 3 insertions(+), 47 deletions(-) diff --git a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee index 525891bb03..f4b9bdbe78 100644 --- a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee +++ b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee @@ -232,23 +232,11 @@ class PeerGradingProblem fetch_submission_essay: () => @backend.post('get_next_submission', {location: @location}, @render_submission) - # finds the scores for each rubric category - get_score_list: () => - # find the number of categories: - num_categories = $('table.rubric tr').length - - score_lst = [] - # get the score for each one - for i in [0..(num_categories-2)] - score = $("input[name='score-selection-#{i}']:checked").val() - score_lst.push(score) - - return score_lst construct_data: () -> data = - rubric_scores: @get_score_list() - score: @grade + rubric_scores: Rubric.get_score_list() + score: Rubric.get_total_score() location: @location submission_id: @essay_id_input.val() submission_key: @submission_key_input.val() @@ -316,7 +304,7 @@ class PeerGradingProblem # called after a grade is selected on the interface graded_callback: (event) => # check to see whether or not any categories have not been scored - if Rubric.check_complete(): + if Rubric.check_complete() # show button if we have scores for all categories @show_submit_button() @@ -439,25 +427,8 @@ class PeerGradingProblem setup_score_selection: (max_score) => - # first, get rid of all the old inputs, if any. - @score_selection_container.html(""" -

    Overall Score

    -

    Choose an overall score for this submission.

    - """) - - # Now create new labels and inputs for each possible score. - for score in [0..max_score] - id = 'score-' + score - label = """""" - - input = """ - - """ # " fix broken parsing in emacs - @score_selection_container.append(input + label) - # And now hook up an event handler again $("input[name='score-selection']").change @graded_callback - $("input[name='grade-selection']").change @graded_callback diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 2d3cafd3e7..117388bab0 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -212,21 +212,6 @@ class @StaffGrading setup_score_selection: => - # first, get rid of all the old inputs, if any. - @grade_selection_container.html(""" -

    Overall Score

    -

    Choose an overall score for this submission.

    - """) - # Now create new labels and inputs for each possible score. - for score in [0..@max_score] - id = 'score-' + score - label = """""" - input = """ - - """ # " fix broken parsing in emacs - @grade_selection_container.append(input + label) - $('.grade-selection').click => @graded_callback() - @score_selection_container.html(@rubric) $('.score-selection').click => @graded_callback() From 1fef6b161b767fddc88aac4194cfac7792b82cd0 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 11:21:56 -0500 Subject: [PATCH 019/444] Add in some better encouragement to write feedback --- lms/templates/instructor/staff_grading.html | 1 + lms/templates/peer_grading/peer_grading_problem.html | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lms/templates/instructor/staff_grading.html b/lms/templates/instructor/staff_grading.html index 56aed5a54a..dcfece34b8 100644 --- a/lms/templates/instructor/staff_grading.html +++ b/lms/templates/instructor/staff_grading.html @@ -75,6 +75,7 @@

    +

    Written Feedback

    diff --git a/lms/templates/peer_grading/peer_grading_problem.html b/lms/templates/peer_grading/peer_grading_problem.html index cb9ed1c0fb..ae630f118e 100644 --- a/lms/templates/peer_grading/peer_grading_problem.html +++ b/lms/templates/peer_grading/peer_grading_problem.html @@ -70,7 +70,9 @@

    - From 2b764eebad0dd892f894ca3abb7f68b0be3e63c1 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 11:27:53 -0500 Subject: [PATCH 020/444] Make the rubric for self-assessment selectable and remove the separate grade selection. --- .../js/src/combinedopenended/display.coffee | 6 +++--- .../xmodule/xmodule/self_assessment_module.py | 2 +- .../src/peer_grading/peer_grading_problem.coffee | 1 - lms/templates/self_assessment_rubric.html | 16 ---------------- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 576fb7290d..9add338137 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -208,9 +208,9 @@ class @CombinedOpenEnded save_assessment: (event) => event.preventDefault() - if @child_state == 'assessing' - checked_assessment = @$('input[name="grade-selection"]:checked') - data = {'assessment' : checked_assessment.val()} + if @child_state == 'assessing' && Rubric.check_complete() + checked_assessment = Rubric.get_total_score() + data = {'assessment' : checked_assessment} $.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) => if response.success @child_state = response.state diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index fb1d306708..a288fa55b3 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -122,7 +122,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): if self.state == self.INITIAL: return '' - rubric_renderer = CombinedOpenEndedRubric(system, True) + rubric_renderer = CombinedOpenEndedRubric(system, False) rubric_html = rubric_renderer.render_rubric(self.rubric) # we'll render it diff --git a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee index f4b9bdbe78..f803c74c7b 100644 --- a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee +++ b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee @@ -426,7 +426,6 @@ class PeerGradingProblem @submit_button.show() setup_score_selection: (max_score) => - # And now hook up an event handler again $("input[name='score-selection']").change @graded_callback diff --git a/lms/templates/self_assessment_rubric.html b/lms/templates/self_assessment_rubric.html index b4fc125232..2986c5041a 100644 --- a/lms/templates/self_assessment_rubric.html +++ b/lms/templates/self_assessment_rubric.html @@ -2,20 +2,4 @@
    ${rubric | n }
    - - % if not read_only: -
    -

    Scoring

    -

    Please select a score below:

    - -
    - %for i in xrange(0,max_score+1): - <% id = "score-{0}".format(i) %> - - - %endfor -
    -
    - % endif - From 8fd32ed5026f9fb4fd287958acef933373c2797e Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 1 Feb 2013 13:01:54 -0500 Subject: [PATCH 021/444] update to show cohort name on create and cohort name on view all threads, added cohort dictionary to convert ids to names --- common/djangoapps/course_groups/cohorts.py | 6 +----- lms/djangoapps/django_comment_client/base/views.py | 2 +- .../django_comment_client/forum/views.py | 11 +++++++++-- lms/static/sass/_discussion.scss | 14 +++++++++----- lms/templates/discussion/_new_post.html | 4 ++-- lms/templates/discussion/_single_thread.html | 5 ++++- .../discussion/_underscore_templates.html | 4 +++- 7 files changed, 29 insertions(+), 17 deletions(-) diff --git a/common/djangoapps/course_groups/cohorts.py b/common/djangoapps/course_groups/cohorts.py index 4d74f70b26..6bb4ad7413 100644 --- a/common/djangoapps/course_groups/cohorts.py +++ b/common/djangoapps/course_groups/cohorts.py @@ -21,8 +21,7 @@ def is_course_cohorted(course_id): Raises: Http404 if the course doesn't exist. """ - #return courses.get_course_by_id(course_id).is_cohorted - return True + return courses.get_course_by_id(course_id).is_cohorted def get_cohort_id(user, course_id): @@ -30,9 +29,6 @@ def get_cohort_id(user, course_id): Given a course id and a user, return the id of the cohort that user is assigned to in that course. If they don't have a cohort, return None. """ - print "\n\n\n\n\n*********************************" - print user - print course_id cohort = get_cohort(user, course_id) return None if cohort is None else cohort.id diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index 7f0809cabb..ce53a8efbb 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -98,7 +98,7 @@ def create_thread(request, course_id, commentable_id): # TODO (vshnayder): once we have more than just cohorts, we'll want to # change this to a single get_group_for_user_and_commentable function # that can do different things depending on the commentable_id - if cached_has_permission(request.user, "see_all_cohorts", course_id) or True: + if cached_has_permission(request.user, "see_all_cohorts", course_id): # admins can optionally choose what group to post as group_id = post.get('group_id', user_group_id) else: diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index 64ba703d55..c92324cbbb 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -152,6 +152,10 @@ def forum_form_discussion(request, course_id): #trending_tags = cc.search_trending_tags( # course_id, #) + cohorts = get_course_cohorts(course_id) + cohort_dictionary = dict() + for c in cohorts: + cohort_dictionary[c.id] = c.name context = { 'csrf': csrf(request)['csrf_token'], @@ -167,12 +171,15 @@ def forum_form_discussion(request, course_id): 'category_map': category_map, 'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict), 'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id), - 'cohorts': get_course_cohorts(course_id), - 'cohort': get_cohort_id(user, course_id), + 'cohorts': cohorts, + 'cohort_map': cohort_dictionary, + 'user_cohort': get_cohort_id(user, course_id), 'cohorted_commentables': get_cohorted_commentables(course_id), 'is_course_cohorted': is_course_cohorted(course_id) } # print "start rendering.." + print "\n\n\n\n*******************************" + print context return render_to_response('discussion/index.html', context) @login_required diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index 2d7e5a8354..a914751280 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -1377,11 +1377,7 @@ body.discussion { margin-bottom: 20px; } - .group-visibility-label { - font-size: 12px; - color:#fff; - font-style: italic; - } + .responses { list-style: none; @@ -2438,3 +2434,11 @@ body.discussion { .discussion-user-threads { @extend .discussion-module } + + +.group-visibility-label { + font-size: 12px; + color:#000; + font-style: italic; + background-color:#fff; + } \ No newline at end of file diff --git a/lms/templates/discussion/_new_post.html b/lms/templates/discussion/_new_post.html index 1bc6148dbe..5b55d409df 100644 --- a/lms/templates/discussion/_new_post.html +++ b/lms/templates/discussion/_new_post.html @@ -55,10 +55,10 @@ %if is_moderator: %for c in cohorts: - + %endfor %else: - + %endif diff --git a/lms/templates/discussion/_single_thread.html b/lms/templates/discussion/_single_thread.html index 7f9dc84564..d4115668a8 100644 --- a/lms/templates/discussion/_single_thread.html +++ b/lms/templates/discussion/_single_thread.html @@ -5,7 +5,10 @@
    -
    This post visible only to group 1.
    + %if thread['group_id'] +
    This post visible only to group ${cohort_dictionary[thread['group_id']]}.
    + %endif + + ${thread['votes']['up_count']}

    ${thread['title']}

    diff --git a/lms/templates/discussion/_underscore_templates.html b/lms/templates/discussion/_underscore_templates.html index 105d677aba..be6e9a5a07 100644 --- a/lms/templates/discussion/_underscore_templates.html +++ b/lms/templates/discussion/_underscore_templates.html @@ -26,7 +26,9 @@

    -
    This post visible only to Group 1.
    + %if thread['group_id'] +
    This post visible only to Group ${cohort_dictionary[thread['group_id']]}.
    + %endif + ${'<%- votes["up_count"] %>'}

    ${'<%- title %>'}

    From 91d9bc05be2f20a8b09ebe1de9b6ae8edf2018d0 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 13:06:12 -0500 Subject: [PATCH 022/444] Fix bug in the callback --- lms/static/coffee/src/peer_grading/peer_grading_problem.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee index f803c74c7b..05d0189ac8 100644 --- a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee +++ b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee @@ -427,7 +427,7 @@ class PeerGradingProblem setup_score_selection: (max_score) => # And now hook up an event handler again - $("input[name='score-selection']").change @graded_callback + $("input[class='score-selection']").change @graded_callback From 00ffbc070adeb605d58efce4478969d620c06b0e Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 14:34:09 -0500 Subject: [PATCH 023/444] Make rubrics spacing smaller and fix a bug in the grading service renderer --- lms/djangoapps/open_ended_grading/grading_service.py | 2 +- lms/static/sass/course/_rubric.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/open_ended_grading/grading_service.py b/lms/djangoapps/open_ended_grading/grading_service.py index f65554a9d6..8e6209bf38 100644 --- a/lms/djangoapps/open_ended_grading/grading_service.py +++ b/lms/djangoapps/open_ended_grading/grading_service.py @@ -115,7 +115,7 @@ class GradingService(object): response_json = json.loads(response) if 'rubric' in response_json: rubric = response_json['rubric'] - rubric_renderer = CombinedOpenEndedRubric(self.system, False) + rubric_renderer = CombinedOpenEndedRubric(self.system, view_only) rubric_html = rubric_renderer.render_rubric(rubric) response_json['rubric'] = rubric_html return response_json diff --git a/lms/static/sass/course/_rubric.scss b/lms/static/sass/course/_rubric.scss index 722a790e6d..5048d70253 100644 --- a/lms/static/sass/course/_rubric.scss +++ b/lms/static/sass/course/_rubric.scss @@ -21,7 +21,7 @@ .rubric-label { position: relative; - padding: 15px 15px 25px; + padding: 0px 15px 15px 15px; width: 130px; min-height: 50px; min-width: 50px; From 69d5c005c30f64346b1f190b04e090f4c29436b3 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 31 Jan 2013 13:27:46 -0500 Subject: [PATCH 024/444] Refactor rubric CSS into a single file. --- .../css/combinedopenended/display.scss | 41 --------------- lms/static/sass/course.scss | 1 + lms/static/sass/course/_rubric.scss | 52 +++++++++++++++++++ lms/static/sass/course/_staff_grading.scss | 47 +---------------- 4 files changed, 54 insertions(+), 87 deletions(-) create mode 100644 lms/static/sass/course/_rubric.scss diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index a4045c9dad..1917471879 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -231,47 +231,6 @@ div.result-container { } } -div.result-container, section.open-ended-child { - .rubric { - margin-bottom:25px; - tr { - margin:10px 0px; - height: 100%; - } - td { - padding: 20px 0px 25px 0px; - margin: 10px 0px; - height: 100%; - } - th { - padding: 5px; - margin: 5px; - } - label, - .view-only { - margin:2px; - position: relative; - padding: 10px 15px 25px 15px; - width: 145px; - height:100%; - display: inline-block; - min-height: 50px; - min-width: 50px; - background-color: #CCC; - font-size: .85em; - } - .grade { - position: absolute; - bottom:0px; - right:0px; - margin:10px; - } - .selected-grade { - background: #666; - color: white; - } - } -} section.open-ended-child { @media print { diff --git a/lms/static/sass/course.scss b/lms/static/sass/course.scss index e900e589b2..d5f620be82 100644 --- a/lms/static/sass/course.scss +++ b/lms/static/sass/course.scss @@ -44,6 +44,7 @@ @import "course/gradebook"; @import "course/tabs"; @import "course/staff_grading"; +@import "course/rubric"; // instructor @import "course/instructor/instructor"; diff --git a/lms/static/sass/course/_rubric.scss b/lms/static/sass/course/_rubric.scss new file mode 100644 index 0000000000..c82d929fac --- /dev/null +++ b/lms/static/sass/course/_rubric.scss @@ -0,0 +1,52 @@ +.rubric { + padding: 40px 0px; + tr { + margin:10px 0px; + height: 100%; + } + td { + padding: 20px 0px 25px 0px; + height: 100%; + border: 1px black solid; + } + th { + padding: 5px; + margin: 5px; + text-align: center; + } + .points-header th { + padding: 0px; + } + label, + .view-only { + margin:2px; + position: relative; + padding: 15px 15px 25px 15px; + width: 130px; + height:100%; + min-height: 50px; + min-width: 50px; + font-size: .9em; + background-color: white; + display: block; + } + .grade { + position: absolute; + bottom:0px; + right:0px; + margin:10px; + } + .selected-grade { + background: #666; + color: white; + } + input[type=radio]:checked + label { + background: #666; + color: white; } + input[class='score-selection'] { + position: relative; + margin-left: 10px; + font-size: 16px; + } +} + diff --git a/lms/static/sass/course/_staff_grading.scss b/lms/static/sass/course/_staff_grading.scss index 177bd9e5e2..4d4da484de 100644 --- a/lms/static/sass/course/_staff_grading.scss +++ b/lms/static/sass/course/_staff_grading.scss @@ -12,7 +12,7 @@ div.peer-grading{ label { margin: 10px; padding: 5px; - display: inline-block; + @include inline-block; min-width: 50px; background-color: #CCC; text-size: 1.5em; @@ -176,49 +176,4 @@ div.peer-grading{ } } padding: 40px; - .rubric { - tr { - margin:10px 0px; - height: 100%; - } - td { - padding: 20px 0px 25px 0px; - height: 100%; - } - th { - padding: 5px; - margin: 5px; - } - label, - .view-only { - margin:2px; - position: relative; - padding: 15px 15px 25px 15px; - width: 150px; - height:100%; - display: inline-block; - min-height: 50px; - min-width: 50px; - background-color: #CCC; - font-size: .9em; - } - .grade { - position: absolute; - bottom:0px; - right:0px; - margin:10px; - } - .selected-grade { - background: #666; - color: white; - } - input[type=radio]:checked + label { - background: #666; - color: white; } - input[class='score-selection'] { - display: none; - } - } - } - From 7e345ce51278810337f15101d931c5cebf35aac8 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 31 Jan 2013 13:28:19 -0500 Subject: [PATCH 025/444] Add scores to the top of the rubric, remove from individual cells --- common/lib/xmodule/xmodule/combined_open_ended_rubric.py | 5 ++++- lms/templates/open_ended_rubric.html | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py index 4380e32d5b..50ec22f033 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py @@ -25,10 +25,13 @@ class CombinedOpenEndedRubric(object): ''' try: rubric_categories = self.extract_categories(rubric_xml) + max_scores = map((lambda cat: cat['options'][-1]['points']), rubric_categories) + max_score = max(max_scores) html = self.system.render_template('open_ended_rubric.html', {'categories' : rubric_categories, 'has_score': self.has_score, - 'view_only': self.view_only}) + 'view_only': self.view_only, + 'max_score': max_score}) except: raise RubricParsingError("[render_rubric] Could not parse the rubric with xml: {0}".format(rubric_xml)) return html diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html index 8d40c7d2b8..b92ad04bde 100644 --- a/lms/templates/open_ended_rubric.html +++ b/lms/templates/open_ended_rubric.html @@ -8,6 +8,14 @@

    Select the criteria you feel best represents this submission in each category.

    % endif + + + % for i in range(max_score + 1): + + % endfor + % for i in range(len(categories)): <% category = categories[i] %> @@ -23,7 +31,6 @@
    % endif ${option['text']} -
    [${option['points']} points]
    % else: From 1fde3c5ec5821b0fe69ac4799bb319659bbb9fa2 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 31 Jan 2013 14:23:17 -0500 Subject: [PATCH 026/444] Make rubric cleaner and visually simpler --- lms/static/sass/course/_rubric.scss | 17 ++++++++--------- lms/templates/open_ended_rubric.html | 6 +++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lms/static/sass/course/_rubric.scss b/lms/static/sass/course/_rubric.scss index c82d929fac..9aa0ca2f2a 100644 --- a/lms/static/sass/course/_rubric.scss +++ b/lms/static/sass/course/_rubric.scss @@ -1,5 +1,5 @@ .rubric { - padding: 40px 0px; + margin: 40px 0px; tr { margin:10px 0px; height: 100%; @@ -8,6 +8,7 @@ padding: 20px 0px 25px 0px; height: 100%; border: 1px black solid; + text-align: center; } th { padding: 5px; @@ -17,13 +18,11 @@ .points-header th { padding: 0px; } - label, - .view-only { - margin:2px; + .rubric-label + { position: relative; - padding: 15px 15px 25px 15px; + padding: 15px 15px 25px; width: 130px; - height:100%; min-height: 50px; min-width: 50px; font-size: .9em; @@ -40,9 +39,9 @@ background: #666; color: white; } - input[type=radio]:checked + label { - background: #666; - color: white; } + input[type=radio]:checked + .rubric-label { + background: white; + color: $base-font-color; } input[class='score-selection'] { position: relative; margin-left: 10px; diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html index b92ad04bde..a2d8d6945c 100644 --- a/lms/templates/open_ended_rubric.html +++ b/lms/templates/open_ended_rubric.html @@ -26,15 +26,15 @@ % if view_only: ## if this is the selected rubric block, show it highlighted % if option['selected']: -
    +
    % else: -
    +
    % endif ${option['text']}
    % else: - + % endif % endfor From 92b7dbdc69e7b2fc177dd2ff2085df22a4d24a25 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 08:43:55 -0500 Subject: [PATCH 027/444] Refactor rubric JS so that we don't have to keep duplicating this code. --- .../js/src/combinedopenended/display.coffee | 29 +++++++++++++++ .../peer_grading/peer_grading_problem.coffee | 15 ++------ .../src/staff_grading/staff_grading.coffee | 37 ++++--------------- 3 files changed, 41 insertions(+), 40 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 594efe2f9b..c4560559c8 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -1,3 +1,32 @@ +class @Rubric + constructor: () -> + + # finds the scores for each rubric category + @get_score_list: () => + # find the number of categories: + num_categories = $('table.rubric tr').length + + score_lst = [] + # get the score for each one + for i in [0..(num_categories-2)] + score = $("input[name='score-selection-#{i}']:checked").val() + score_lst.push(score) + + return score_lst + + @get_total_score: () -> + score_lst = @get_score_list() + + @check_complete: () -> + # check to see whether or not any categories have not been scored + num_categories = $('table.rubric tr').length + # -2 because we want to skip the header + for i in [0..(num_categories-2)] + score = $("input[name='score-selection-#{i}']:checked").val() + if score == undefined + return false + return true + class @CombinedOpenEnded constructor: (element) -> @element=element diff --git a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee index c4b87eb30e..525891bb03 100644 --- a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee +++ b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee @@ -239,7 +239,7 @@ class PeerGradingProblem score_lst = [] # get the score for each one - for i in [0..(num_categories-1)] + for i in [0..(num_categories-2)] score = $("input[name='score-selection-#{i}']:checked").val() score_lst.push(score) @@ -315,17 +315,10 @@ class PeerGradingProblem # called after a grade is selected on the interface graded_callback: (event) => - @grade = $("input[name='grade-selection']:checked").val() - if @grade == undefined - return # check to see whether or not any categories have not been scored - num_categories = $('table.rubric tr').length - for i in [0..(num_categories-1)] - score = $("input[name='score-selection-#{i}']:checked").val() - if score == undefined - return - # show button if we have scores for all categories - @show_submit_button() + if Rubric.check_complete(): + # show button if we have scores for all categories + @show_submit_button() diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 005a8e682e..2d3cafd3e7 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -232,35 +232,14 @@ class @StaffGrading graded_callback: () => - @grade = $("input[name='grade-selection']:checked").val() - if @grade == undefined - return - # check to see whether or not any categories have not been scored - num_categories = $('table.rubric tr').length - for i in [0..(num_categories-1)] - score = $("input[name='score-selection-#{i}']:checked").val() - if score == undefined - return - # show button if we have scores for all categories - @state = state_graded - @submit_button.show() + # show button if we have scores for all categories + if Rubric.check_complete() + @state = state_graded + @submit_button.show() set_button_text: (text) => @action_button.attr('value', text) - # finds the scores for each rubric category - get_score_list: () => - # find the number of categories: - num_categories = $('table.rubric tr').length - - score_lst = [] - # get the score for each one - for i in [0..(num_categories-1)] - score = $("input[name='score-selection-#{i}']:checked").val() - score_lst.push(score) - - return score_lst - ajax_callback: (response) => # always clear out errors and messages on transition. @error_msg = '' @@ -285,8 +264,8 @@ class @StaffGrading skip_and_get_next: () => data = - score: @grade - rubric_scores: @get_score_list() + score: Rubric.get_total_score() + rubric_scores: Rubric.get_score_list() feedback: @feedback_area.val() submission_id: @submission_id location: @location @@ -299,8 +278,8 @@ class @StaffGrading submit_and_get_next: () -> data = - score: @grade - rubric_scores: @get_score_list() + score: Rubric.get_total_score() + rubric_scores: Rubric.get_score_list() feedback: @feedback_area.val() submission_id: @submission_id location: @location From 0bae98398202d798f69154aeaa77e99581739f13 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 08:44:08 -0500 Subject: [PATCH 028/444] Visual updates to rubric --- lms/static/sass/course/_rubric.scss | 3 ++- lms/templates/open_ended_rubric.html | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lms/static/sass/course/_rubric.scss b/lms/static/sass/course/_rubric.scss index 9aa0ca2f2a..722a790e6d 100644 --- a/lms/static/sass/course/_rubric.scss +++ b/lms/static/sass/course/_rubric.scss @@ -35,7 +35,8 @@ right:0px; margin:10px; } - .selected-grade { + .selected-grade, + .selected-grade .rubric-label { background: #666; color: white; } diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html index a2d8d6945c..eb3fc564b4 100644 --- a/lms/templates/open_ended_rubric.html +++ b/lms/templates/open_ended_rubric.html @@ -22,14 +22,14 @@
    % for j in range(len(category['options'])): <% option = category['options'][j] %> + %if option['selected']: + '.format(header))>0) - self.assertTrue(response.content.find(''.format(username))>=0) + self.assertTrue(response.content.find(''.format(header)) > 0) + self.assertTrue(response.content.find(''.format(username)) >= 0) # concatenate all roles for user, in sorted order: added_roles.append(rolename) added_roles.sort() roles = ', '.join(added_roles) - self.assertTrue(response.content.find(''.format(roles))>=0, 'not finding roles "{0}"'.format(roles)) - - + self.assertTrue(response.content.find(''.format(roles)) >= 0, 'not finding roles "{0}"'.format(roles)) diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index a707506045..4faf814bc9 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -50,9 +50,11 @@ template_imports = {'urllib': urllib} FORUM_ROLE_ADD = 'add' FORUM_ROLE_REMOVE = 'remove' + def split_by_comma_and_whitespace(s): return re.split(r'[\s,]', s) + @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) def instructor_dashboard(request, course_id): @@ -69,11 +71,11 @@ def instructor_dashboard(request, course_id): # the instructor dashboard page is modal: grades, psychometrics, admin # keep that state in request.session (defaults to grades mode) - idash_mode = request.POST.get('idash_mode','') + idash_mode = request.POST.get('idash_mode', '') if idash_mode: request.session['idash_mode'] = idash_mode else: - idash_mode = request.session.get('idash_mode','Grades') + idash_mode = request.session.get('idash_mode', 'Grades') def escape(s): """escape HTML special characters in string""" @@ -130,7 +132,7 @@ def instructor_dashboard(request, course_id): # process actions from form POST action = request.POST.get('action', '') - use_offline = request.POST.get('use_offline_grades',False) + use_offline = request.POST.get('use_offline_grades', False) if settings.MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD']: if 'GIT pull' in action: @@ -155,12 +157,12 @@ def instructor_dashboard(request, course_id): course_errors = modulestore().get_item_errors(course.location) msg += '
      ' for cmsg, cerr in course_errors: - msg += "
    • {0}:
      {1}
      ".format(cmsg,escape(cerr)) + msg += "
    • {0}:
      {1}
      ".format(cmsg, escape(cerr)) msg += '
    ' except Exception as err: msg += '

    Error: {0}

    '.format(escape(err)) - if action == 'Dump list of enrolled students' or action=='List enrolled students': + if action == 'Dump list of enrolled students' or action == 'List enrolled students': log.debug(action) datatable = get_student_grade_summary_data(request, course, course_id, get_grades=False, use_offline=use_offline) datatable['title'] = 'List of students enrolled in {0}'.format(course_id) @@ -195,44 +197,44 @@ def instructor_dashboard(request, course_id): elif "Reset student's attempts" in action: # get the form data - unique_student_identifier=request.POST.get('unique_student_identifier','') - problem_to_reset=request.POST.get('problem_to_reset','') + unique_student_identifier = request.POST.get('unique_student_identifier', '') + problem_to_reset = request.POST.get('problem_to_reset', '') - if problem_to_reset[-4:]==".xml": - problem_to_reset=problem_to_reset[:-4] + if problem_to_reset[-4:] == ".xml": + problem_to_reset = problem_to_reset[:-4] # try to uniquely id student by email address or username try: if "@" in unique_student_identifier: - student_to_reset=User.objects.get(email=unique_student_identifier) + student_to_reset = User.objects.get(email=unique_student_identifier) else: - student_to_reset=User.objects.get(username=unique_student_identifier) - msg+="Found a single student to reset. " + student_to_reset = User.objects.get(username=unique_student_identifier) + msg += "Found a single student to reset. " except: - student_to_reset=None - msg+="Couldn't find student with that email or username. " + student_to_reset = None + msg += "Couldn't find student with that email or username. " if student_to_reset is not None: # find the module in question try: - (org, course_name, run)=course_id.split("/") - module_state_key="i4x://"+org+"/"+course_name+"/problem/"+problem_to_reset - module_to_reset=StudentModule.objects.get(student_id=student_to_reset.id, + (org, course_name, run) = course_id.split("/") + module_state_key = "i4x://" + org + "/" + course_name + "/problem/" + problem_to_reset + module_to_reset = StudentModule.objects.get(student_id=student_to_reset.id, course_id=course_id, module_state_key=module_state_key) - msg+="Found module to reset. " + msg += "Found module to reset. " except Exception as e: - msg+="Couldn't find module with that urlname. " + msg += "Couldn't find module with that urlname. " # modify the problem's state try: # load the state json - problem_state=json.loads(module_to_reset.state) - old_number_of_attempts=problem_state["attempts"] - problem_state["attempts"]=0 + problem_state = json.loads(module_to_reset.state) + old_number_of_attempts = problem_state["attempts"] + problem_state["attempts"] = 0 # save - module_to_reset.state=json.dumps(problem_state) + module_to_reset.state = json.dumps(problem_state) module_to_reset.save() track.views.server_track(request, '{instructor} reset attempts from {old_attempts} to 0 for {student} on problem {problem} in {course}'.format( @@ -243,19 +245,19 @@ def instructor_dashboard(request, course_id): course=course_id), {}, page='idashboard') - msg+="Module state successfully reset!" + msg += "Module state successfully reset!" except: - msg+="Couldn't reset module state. " + msg += "Couldn't reset module state. " elif "Get link to student's progress page" in action: - unique_student_identifier=request.POST.get('unique_student_identifier','') + unique_student_identifier = request.POST.get('unique_student_identifier', '') try: if "@" in unique_student_identifier: - student_to_reset=User.objects.get(email=unique_student_identifier) + student_to_reset = User.objects.get(email=unique_student_identifier) else: - student_to_reset=User.objects.get(username=unique_student_identifier) - progress_url=reverse('student_progress',kwargs={'course_id':course_id,'student_id': student_to_reset.id}) + student_to_reset = User.objects.get(username=unique_student_identifier) + progress_url = reverse('student_progress', kwargs={'course_id': course_id, 'student_id': student_to_reset.id}) track.views.server_track(request, '{instructor} requested progress page for {student} in {course}'.format( student=student_to_reset, @@ -263,18 +265,18 @@ def instructor_dashboard(request, course_id): course=course_id), {}, page='idashboard') - msg+=" Progress page for username: {1} with email address: {2}.".format(progress_url,student_to_reset.username,student_to_reset.email) + msg += " Progress page for username: {1} with email address: {2}.".format(progress_url, student_to_reset.username, student_to_reset.email) except: - msg+="Couldn't find student with that username. " + msg += "Couldn't find student with that username. " #---------------------------------------- # export grades to remote gradebook - elif action=='List assignments available in remote gradebook': + elif action == 'List assignments available in remote gradebook': msg2, datatable = _do_remote_gradebook(request.user, course, 'get-assignments') msg += msg2 - elif action=='List assignments available for this course': + elif action == 'List assignments available for this course': log.debug(action) allgrades = get_student_grade_summary_data(request, course, course_id, get_grades=True, use_offline=use_offline) @@ -285,11 +287,11 @@ def instructor_dashboard(request, course_id): msg += 'assignments=
    %s
    ' % assignments - elif action=='List enrolled students matching remote gradebook': + elif action == 'List enrolled students matching remote gradebook': stud_data = get_student_grade_summary_data(request, course, course_id, get_grades=False, use_offline=use_offline) msg2, rg_stud_data = _do_remote_gradebook(request.user, course, 'get-membership') datatable = {'header': ['Student email', 'Match?']} - rg_students = [ x['email'] for x in rg_stud_data['retdata'] ] + rg_students = [x['email'] for x in rg_stud_data['retdata']] def domatch(x): return 'yes' if x.email in rg_students else 'No' datatable['data'] = [[x.email, domatch(x)] for x in stud_data['students']] @@ -300,7 +302,7 @@ def instructor_dashboard(request, course_id): log.debug(action) datatable = {} - aname = request.POST.get('assignment_name','') + aname = request.POST.get('assignment_name', '') if not aname: msg += "Please enter an assignment name" else: @@ -476,13 +478,13 @@ def instructor_dashboard(request, course_id): elif action == 'Enroll student': - student = request.POST.get('enstudent','') + student = request.POST.get('enstudent', '') ret = _do_enroll_students(course, course_id, student) datatable = ret['datatable'] elif action == 'Un-enroll student': - student = request.POST.get('enstudent','') + student = request.POST.get('enstudent', '') datatable = {} isok = False cea = CourseEnrollmentAllowed.objects.filter(course_id=course_id, email=student) @@ -506,7 +508,7 @@ def instructor_dashboard(request, course_id): elif action == 'Enroll multiple students': - students = request.POST.get('enroll_multiple','') + students = request.POST.get('enroll_multiple', '') ret = _do_enroll_students(course, course_id, students) datatable = ret['datatable'] @@ -519,8 +521,8 @@ def instructor_dashboard(request, course_id): 'Overload enrollment list using remote gradebook', 'Merge enrollment list with remote gradebook']: - section = request.POST.get('gradebook_section','') - msg2, datatable = _do_remote_gradebook(request.user, course, 'get-membership', dict(section=section) ) + section = request.POST.get('gradebook_section', '') + msg2, datatable = _do_remote_gradebook(request.user, course, 'get-membership', dict(section=section)) msg += msg2 if not 'List' in action: @@ -539,7 +541,7 @@ def instructor_dashboard(request, course_id): msg += nmsg track.views.server_track(request, 'psychometrics {0}'.format(problem), {}, page='idashboard') - if idash_mode=='Psychometrics': + if idash_mode == 'Psychometrics': problems = psychoanalyze.problems_with_psychometric_data(course_id) @@ -563,10 +565,10 @@ def instructor_dashboard(request, course_id): 'problems': problems, # psychometrics 'plots': plots, # psychometrics 'course_errors': modulestore().get_item_errors(course.location), - 'djangopid' : os.getpid(), - 'mitx_version' : getattr(settings,'MITX_VERSION_STRING',''), - 'offline_grade_log' : offline_grades_available(course_id), - 'cohorts_ajax_url' : reverse('cohorts', kwargs={'course_id': course_id}), + 'djangopid': os.getpid(), + 'mitx_version': getattr(settings, 'MITX_VERSION_STRING', ''), + 'offline_grade_log': offline_grades_available(course_id), + 'cohorts_ajax_url': reverse('cohorts', kwargs={'course_id': course_id}), } return render_to_response('courseware/instructor_dashboard.html', context) @@ -576,17 +578,17 @@ def _do_remote_gradebook(user, course, action, args=None, files=None): ''' Perform remote gradebook action. Returns msg, datatable. ''' - rg = course.metadata.get('remote_gradebook','') + rg = course.metadata.get('remote_gradebook', '') if not rg: msg = "No remote gradebook defined in course metadata" return msg, {} - rgurl = settings.MITX_FEATURES.get('REMOTE_GRADEBOOK_URL','') + rgurl = settings.MITX_FEATURES.get('REMOTE_GRADEBOOK_URL', '') if not rgurl: msg = "No remote gradebook url defined in settings.MITX_FEATURES" return msg, {} - rgname = rg.get('name','') + rgname = rg.get('name', '') if not rgname: msg = "No gradebook name defined in course remote_gradebook metadata" return msg, {} @@ -606,8 +608,8 @@ def _do_remote_gradebook(user, course, action, args=None, files=None): msg += "
    data=%s" % data return msg, {} - msg = '
    %s
    ' % retdict['msg'].replace('\n','
    ') - retdata = retdict['data'] # a list of dicts + msg = '
    %s
    ' % retdict['msg'].replace('\n', '
    ') + retdata = retdict['data'] # a list of dicts if retdata: datatable = {'header': retdata[0].keys()} @@ -619,6 +621,7 @@ def _do_remote_gradebook(user, course, action, args=None, files=None): return msg, datatable + def _list_course_forum_members(course_id, rolename, datatable): """ Fills in datatable with forum membership information, for a given role, @@ -672,7 +675,7 @@ def _update_forum_role_membership(uname, course, rolename, add_or_remove): log.debug('rolename={0}'.format(rolename)) if add_or_remove == FORUM_ROLE_REMOVE: if not alreadyexists: - msg ='Error: user "{0}" does not have rolename "{1}", cannot remove'.format(uname, rolename) + msg = 'Error: user "{0}" does not have rolename "{1}", cannot remove'.format(uname, rolename) else: user.roles.remove(role) msg = 'Removed "{0}" from "{1}" forum role = "{2}"'.format(user, course.id, rolename) @@ -688,6 +691,7 @@ def _update_forum_role_membership(uname, course, rolename, add_or_remove): return msg + def _group_members_table(group, title, course_id): """ Return a data table of usernames and names of users in group_name. @@ -756,6 +760,7 @@ def add_user_to_group(request, username_or_email, group, group_title, event_name """ return _add_or_remove_user_group(request, username_or_email, group, group_title, event_name, True) + def remove_user_from_group(request, username_or_email, group, group_title, event_name): """ Look up the given user by username (if no '@') or email (otherwise), and remove them from group. @@ -809,22 +814,22 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True, data = [] for student in enrolled_students: - datarow = [ student.id, student.username, student.profile.name, student.email ] + datarow = [student.id, student.username, student.profile.name, student.email] try: datarow.append(student.externalauthmap.external_email) - except: # ExternalAuthMap.DoesNotExist + except: # ExternalAuthMap.DoesNotExist datarow.append('') if get_grades: gradeset = student_grades(student, request, course, keep_raw_scores=get_raw_scores, use_offline=use_offline) - log.debug('student={0}, gradeset={1}'.format(student,gradeset)) + log.debug('student={0}, gradeset={1}'.format(student, gradeset)) if get_raw_scores: # TODO (ichuang) encode Score as dict instead of as list, so score[0] -> score['earned'] - sgrades = [(getattr(score,'earned','') or score[0]) for score in gradeset['raw_scores']] + sgrades = [(getattr(score, 'earned', '') or score[0]) for score in gradeset['raw_scores']] else: sgrades = [x['percent'] for x in gradeset['section_breakdown']] datarow += sgrades - student.grades = sgrades # store in student object + student.grades = sgrades # store in student object data.append(datarow) datatable['data'] = data @@ -832,6 +837,7 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True, #----------------------------------------------------------------------------- + @cache_control(no_cache=True, no_store=True, must_revalidate=True) def gradebook(request, course_id): """ @@ -886,9 +892,9 @@ def _do_enroll_students(course, course_id, students, overload=False): if '' in new_students: new_students.remove('') - status = dict([x,'unprocessed'] for x in new_students) + status = dict([x, 'unprocessed'] for x in new_students) - if overload: # delete all but staff + if overload: # delete all but staff todelete = CourseEnrollment.objects.filter(course_id=course_id) for ce in todelete: if not has_access(ce.user, course, 'staff') and ce.user.email.lower() not in new_students_lc: @@ -903,7 +909,7 @@ def _do_enroll_students(course, course_id, students, overload=False): for student in new_students: try: - user=User.objects.get(email=student) + user = User.objects.get(email=student) except User.DoesNotExist: # user not signed up yet, put in pending enrollment allowed table if CourseEnrollmentAllowed.objects.filter(email=student, course_id=course_id): @@ -928,9 +934,9 @@ def _do_enroll_students(course, course_id, students, overload=False): datatable['data'] = [[x, status[x]] for x in status] datatable['title'] = 'Enrollment of students' - def sf(stat): return [x for x in status if status[x]==stat] + def sf(stat): return [x for x in status if status[x] == stat] - data = dict(added=sf('added'), rejected=sf('rejected')+sf('exists'), + data = dict(added=sf('added'), rejected=sf('rejected') + sf('exists'), deleted=sf('deleted'), datatable=datatable) return data @@ -1013,5 +1019,5 @@ def compute_course_stats(course): walk(c) walk(course) - stats = dict(counts) # number of each kind of module + stats = dict(counts) # number of each kind of module return stats diff --git a/lms/djangoapps/licenses/migrations/0001_initial.py b/lms/djangoapps/licenses/migrations/0001_initial.py index bdc1d3ead4..365046272d 100644 --- a/lms/djangoapps/licenses/migrations/0001_initial.py +++ b/lms/djangoapps/licenses/migrations/0001_initial.py @@ -115,4 +115,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['licenses'] \ No newline at end of file + complete_apps = ['licenses'] diff --git a/lms/djangoapps/lms_migration/management/commands/create_groups.py b/lms/djangoapps/lms_migration/management/commands/create_groups.py index 7b52795606..95c9e4238b 100644 --- a/lms/djangoapps/lms_migration/management/commands/create_groups.py +++ b/lms/djangoapps/lms_migration/management/commands/create_groups.py @@ -4,7 +4,10 @@ # # Create all staff_* groups for classes in data directory. -import os, sys, string, re +import os +import sys +import string +import re from django.core.management.base import BaseCommand from django.conf import settings @@ -12,6 +15,7 @@ from django.contrib.auth.models import User, Group from path import path from lxml import etree + def create_groups(): ''' Create staff and instructor groups for all classes in the data_dir @@ -26,7 +30,7 @@ def create_groups(): continue if not os.path.isdir(path(data_dir) / course_dir): continue - + cxfn = path(data_dir) / course_dir / 'course.xml' try: coursexml = etree.parse(cxfn) @@ -38,11 +42,12 @@ def create_groups(): if course is None: print "oops, can't get course id for %s" % course_dir continue - print "course=%s for course_dir=%s" % (course,course_dir) - + print "course=%s for course_dir=%s" % (course, course_dir) + create_group('staff_%s' % course) # staff group create_group('instructor_%s' % course) # instructor group (can manage staff group list) + def create_group(gname): if Group.objects.filter(name=gname): print " group exists for %s" % gname @@ -51,6 +56,7 @@ def create_group(gname): g.save() print " created group %s" % gname + class Command(BaseCommand): help = "Create groups associated with all courses in data_dir." diff --git a/lms/djangoapps/lms_migration/management/commands/create_user.py b/lms/djangoapps/lms_migration/management/commands/create_user.py index 7d39accc44..86b355e571 100644 --- a/lms/djangoapps/lms_migration/management/commands/create_user.py +++ b/lms/djangoapps/lms_migration/management/commands/create_user.py @@ -4,7 +4,10 @@ # # Create user. Prompt for groups and ExternalAuthMap -import os, sys, string, re +import os +import sys +import string +import re import datetime from getpass import getpass import json @@ -16,6 +19,7 @@ from student.models import UserProfile, Registration from external_auth.models import ExternalAuthMap from django.contrib.auth.models import User, Group + class MyCompleter(object): # Custom completer def __init__(self, options): @@ -24,23 +28,25 @@ class MyCompleter(object): # Custom completer def complete(self, text, state): if state == 0: # on first trigger, build possible matches if text: # cache matches (entries that start with entered text) - self.matches = [s for s in self.options + self.matches = [s for s in self.options if s and s.startswith(text)] else: # no text entered, all matches possible self.matches = self.options[:] # return match indexed by state - try: + try: return self.matches[state] except IndexError: return None + def GenPasswd(length=8, chars=string.letters + string.digits): return ''.join([choice(chars) for i in range(length)]) #----------------------------------------------------------------------------- # main command + class Command(BaseCommand): help = "Create user, interactively; can add ExternalAuthMap for MIT user if email@MIT.EDU resolves properly." @@ -52,27 +58,27 @@ class Command(BaseCommand): print "username %s already taken" % uname else: break - + make_eamap = False - if raw_input('Create MIT ExternalAuth? [n] ').lower()=='y': + if raw_input('Create MIT ExternalAuth? [n] ').lower() == 'y': email = '%s@MIT.EDU' % uname if not email.endswith('@MIT.EDU'): print "Failed - email must be @MIT.EDU" sys.exit(-1) mit_domain = 'ssl:MIT' - if ExternalAuthMap.objects.filter(external_id = email, external_domain = mit_domain): + if ExternalAuthMap.objects.filter(external_id=email, external_domain=mit_domain): print "Failed - email %s already exists as external_id" % email sys.exit(-1) make_eamap = True password = GenPasswd(12) - + # get name from kerberos try: kname = os.popen("finger %s | grep 'name:'" % email).read().strip().split('name: ')[1].strip() except: kname = '' name = raw_input('Full name: [%s] ' % kname).strip() - if name=='': + if name == '': name = kname print "name = %s" % name else: @@ -82,17 +88,17 @@ class Command(BaseCommand): if password == password2: break print "Oops, passwords do not match, please retry" - + while True: email = raw_input('email: ') if User.objects.filter(email=email): print "email %s already taken" % email else: break - + name = raw_input('Full name: ') - - + + user = User(username=uname, email=email, is_active=True) user.set_password(password) try: @@ -100,41 +106,41 @@ class Command(BaseCommand): except IntegrityError: print "Oops, failed to create user %s, IntegrityError" % user raise - + r = Registration() r.register(user) - + up = UserProfile(user=user) up.name = name up.save() - + if make_eamap: - credentials = "/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA v1/CN=%s/emailAddress=%s" % (name,email) - eamap = ExternalAuthMap(external_id = email, - external_email = email, - external_domain = mit_domain, - external_name = name, - internal_password = password, - external_credentials = json.dumps(credentials), + credentials = "/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA v1/CN=%s/emailAddress=%s" % (name, email) + eamap = ExternalAuthMap(external_id=email, + external_email=email, + external_domain=mit_domain, + external_name=name, + internal_password=password, + external_credentials=json.dumps(credentials), ) eamap.user = user eamap.dtsignup = datetime.datetime.now() eamap.save() - + print "User %s created successfully!" % user - - if not raw_input('Add user %s to any groups? [n] ' % user).lower()=='y': + + if not raw_input('Add user %s to any groups? [n] ' % user).lower() == 'y': sys.exit(0) - + print "Here are the groups available:" - + groups = [str(g.name) for g in Group.objects.all()] print groups - + completer = MyCompleter(groups) readline.set_completer(completer.complete) readline.parse_and_bind('tab: complete') - + while True: gname = raw_input("Add group (tab to autocomplete, empty line to end): ") if not gname: @@ -144,6 +150,6 @@ class Command(BaseCommand): continue g = Group.objects.get(name=gname) user.groups.add(g) - print "Added %s to group %s" % (user,g) - + print "Added %s to group %s" % (user, g) + print "Done!" diff --git a/lms/djangoapps/lms_migration/management/commands/manage_course_groups.py b/lms/djangoapps/lms_migration/management/commands/manage_course_groups.py index f3a39db5ca..b63ef7859b 100644 --- a/lms/djangoapps/lms_migration/management/commands/manage_course_groups.py +++ b/lms/djangoapps/lms_migration/management/commands/manage_course_groups.py @@ -4,7 +4,10 @@ # # interactively list and edit membership in course staff and instructor groups -import os, sys, string, re +import os +import sys +import string +import re import datetime from getpass import getpass import json @@ -17,26 +20,27 @@ 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) - + 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: + if uall.count() < 50: print "----" print "List of All Users: %s" % [str(x.username) for x in uall] print "----" @@ -44,24 +48,24 @@ class Command(BaseCommand): 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) + + 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: @@ -71,6 +75,3 @@ class Command(BaseCommand): continue print "adding %s to group %s" % (user, group) user.groups.add(group) - - - diff --git a/lms/djangoapps/lms_migration/migrate.py b/lms/djangoapps/lms_migration/migrate.py index ecde31d6dd..9cdc783bb9 100644 --- a/lms/djangoapps/lms_migration/migrate.py +++ b/lms/djangoapps/lms_migration/migrate.py @@ -22,29 +22,32 @@ log = logging.getLogger("mitx.lms_migrate") LOCAL_DEBUG = True ALLOWED_IPS = settings.LMS_MIGRATION_ALLOWED_IPS + def escape(s): """escape HTML special characters in string""" - return str(s).replace('<','<').replace('>','>') + return str(s).replace('<', '<').replace('>', '>') + def getip(request): ''' Extract IP address of requester from header, even if behind proxy ''' - ip = request.META.get('HTTP_X_REAL_IP','') # nginx reverse proxy + ip = request.META.get('HTTP_X_REAL_IP', '') # nginx reverse proxy if not ip: - ip = request.META.get('REMOTE_ADDR','None') + ip = request.META.get('REMOTE_ADDR', 'None') return ip def get_commit_id(course): - return course.metadata.get('GIT_COMMIT_ID','No commit id') + return course.metadata.get('GIT_COMMIT_ID', 'No commit id') # getattr(def_ms.courses[reload_dir], 'GIT_COMMIT_ID','No commit id') -def set_commit_id(course,commit_id): +def set_commit_id(course, commit_id): course.metadata['GIT_COMMIT_ID'] = commit_id # setattr(def_ms.courses[reload_dir], 'GIT_COMMIT_ID', new_commit_id) + def manage_modulestores(request, reload_dir=None, commit_id=None): ''' Manage the static in-memory modulestores. @@ -65,7 +68,7 @@ def manage_modulestores(request, reload_dir=None, commit_id=None): html += '

    IP address: %s

    ' % ip html += '

    User: %s

    ' % request.user html += '

    My pid: %s

    ' % os.getpid() - log.debug('request from ip=%s, user=%s' % (ip,request.user)) + log.debug('request from ip=%s, user=%s' % (ip, request.user)) if not (ip in ALLOWED_IPS or 'any' in ALLOWED_IPS): if request.user and request.user.is_staff: @@ -89,7 +92,7 @@ def manage_modulestores(request, reload_dir=None, commit_id=None): log.debug('commit_id="%s"' % commit_id) log.debug('current_commit_id="%s"' % current_commit_id) - if (commit_id is not None) and (commit_id==current_commit_id): + if (commit_id is not None) and (commit_id == current_commit_id): html += "

    Already at commit id %s for %s

    " % (commit_id, reload_dir) track.views.server_track(request, 'reload %s skipped already at %s (pid=%s)' % (reload_dir, @@ -100,7 +103,7 @@ def manage_modulestores(request, reload_dir=None, commit_id=None): else: html += '

    Reloaded course directory "%s"

    ' % reload_dir def_ms.try_load_course(reload_dir) - gdir = settings.DATA_DIR / reload_dir + gdir = settings.DATA_DIR / reload_dir new_commit_id = os.popen('cd %s; git log -n 1 | head -1' % gdir).read().strip().split(' ')[1] set_commit_id(def_ms.courses[reload_dir], new_commit_id) html += '

    commit_id=%s

    ' % new_commit_id @@ -121,21 +124,21 @@ def manage_modulestores(request, reload_dir=None, commit_id=None): #---------------------------------------- - dumpfields = ['definition','location','metadata'] + dumpfields = ['definition', 'location', 'metadata'] for cdir, course in def_ms.courses.items(): html += '
    ' - html += '

    Course: %s (%s)

    ' % (course.display_name,cdir) + html += '

    Course: %s (%s)

    ' % (course.display_name, cdir) html += '

    commit_id=%s

    ' % get_commit_id(course) for field in dumpfields: - data = getattr(course,field) + data = getattr(course, field) html += '

    %s

    ' % field - if type(data)==dict: + if type(data) == dict: html += '
      ' - for k,v in data.items(): - html += '
    • %s:%s
    • ' % (escape(k),escape(v)) + for k, v in data.items(): + html += '
    • %s:%s
    • ' % (escape(k), escape(v)) html += '
    ' else: html += '
    • %s
    ' % escape(data) @@ -159,6 +162,7 @@ def manage_modulestores(request, reload_dir=None, commit_id=None): html += "" return HttpResponse(html) + @csrf_exempt def gitreload(request, reload_dir=None): ''' @@ -172,8 +176,8 @@ def gitreload(request, reload_dir=None): html += '

    IP address: %s ' % ip html += '

    User: %s ' % request.user - ALLOWED_IPS = [] # allow none by default - if hasattr(settings,'ALLOWED_GITRELOAD_IPS'): # allow override in settings + ALLOWED_IPS = [] # allow none by default + if hasattr(settings, 'ALLOWED_GITRELOAD_IPS'): # allow override in settings ALLOWED_IPS = settings.ALLOWED_GITRELOAD_IPS if not (ip in ALLOWED_IPS or 'any' in ALLOWED_IPS): @@ -182,9 +186,9 @@ def gitreload(request, reload_dir=None): else: html += 'Permission denied' html += "" - log.debug('request denied from %s, ALLOWED_IPS=%s' % (ip,ALLOWED_IPS)) - return HttpResponse(html) - + log.debug('request denied from %s, ALLOWED_IPS=%s' % (ip, ALLOWED_IPS)) + return HttpResponse(html) + #---------------------------------------- # see if request is from github (POST with JSON) @@ -195,19 +199,19 @@ def gitreload(request, reload_dir=None): log.debug("gitargs=%s" % gitargs) reload_dir = gitargs['repository']['name'] log.debug("github reload_dir=%s" % reload_dir) - gdir = settings.DATA_DIR / reload_dir + gdir = settings.DATA_DIR / reload_dir if not os.path.exists(gdir): log.debug("====> ERROR in gitreload - no such directory %s" % reload_dir) return HttpResponse('Error') cmd = "cd %s; git reset --hard HEAD; git clean -f -d; git pull origin; chmod g+w course.xml" % gdir log.debug(os.popen(cmd).read()) - if hasattr(settings,'GITRELOAD_HOOK'): # hit this hook after reload, if set + if hasattr(settings, 'GITRELOAD_HOOK'): # hit this hook after reload, if set gh = settings.GITRELOAD_HOOK if gh: - ghurl = '%s/%s' % (gh,reload_dir) + ghurl = '%s/%s' % (gh, reload_dir) r = requests.get(ghurl) log.debug("GITRELOAD_HOOK to %s: %s" % (ghurl, r.text)) - + #---------------------------------------- # reload course if specified @@ -220,4 +224,4 @@ def gitreload(request, reload_dir=None): def_ms.try_load_course(reload_dir) track.views.server_track(request, 'reloaded %s' % reload_dir, {}, page='migrate') - return HttpResponse(html) + return HttpResponse(html) diff --git a/lms/djangoapps/open_ended_grading/controller_query_service.py b/lms/djangoapps/open_ended_grading/controller_query_service.py index 5d2c40b6ce..83d5617bd2 100644 --- a/lms/djangoapps/open_ended_grading/controller_query_service.py +++ b/lms/djangoapps/open_ended_grading/controller_query_service.py @@ -12,12 +12,13 @@ from mitxmako.shortcuts import render_to_string log = logging.getLogger(__name__) + class ControllerQueryService(GradingService): """ Interface to staff grading backend. """ def __init__(self, config): - config['system'] = ModuleSystem(None,None,None,render_to_string,None) + config['system'] = ModuleSystem(None, None, None, render_to_string, None) super(ControllerQueryService, self).__init__(config) self.check_eta_url = self.url + '/get_submission_eta/' self.is_unique_url = self.url + '/is_name_unique/' @@ -29,34 +30,34 @@ class ControllerQueryService(GradingService): def check_if_name_is_unique(self, location, problem_id, course_id): params = { 'course_id': course_id, - 'location' : location, - 'problem_id' : problem_id + 'location': location, + 'problem_id': problem_id } response = self.get(self.is_unique_url, params) return response def check_for_eta(self, location): params = { - 'location' : location, + 'location': location, } response = self.get(self.check_eta_url, params) return response def check_combined_notifications(self, course_id, student_id, user_is_staff, last_time_viewed): - params= { - 'student_id' : student_id, - 'course_id' : course_id, - 'user_is_staff' : user_is_staff, - 'last_time_viewed' : last_time_viewed, + params = { + 'student_id': student_id, + 'course_id': course_id, + 'user_is_staff': user_is_staff, + 'last_time_viewed': last_time_viewed, } log.debug(self.combined_notifications_url) - response = self.get(self.combined_notifications_url,params) + response = self.get(self.combined_notifications_url, params) return response def get_grading_status_list(self, course_id, student_id): params = { - 'student_id' : student_id, - 'course_id' : course_id, + 'student_id': student_id, + 'course_id': course_id, } response = self.get(self.grading_status_list_url, params) @@ -64,7 +65,7 @@ class ControllerQueryService(GradingService): def get_flagged_problem_list(self, course_id): params = { - 'course_id' : course_id, + 'course_id': course_id, } response = self.get(self.flagged_problem_list_url, params) @@ -72,12 +73,11 @@ class ControllerQueryService(GradingService): def take_action_on_flags(self, course_id, student_id, submission_id, action_type): params = { - 'course_id' : course_id, - 'student_id' : student_id, - 'submission_id' : submission_id, - 'action_type' : action_type + 'course_id': course_id, + 'student_id': student_id, + 'submission_id': submission_id, + 'action_type': action_type } response = self.post(self.take_action_on_flags_url, params) return response - diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py index b8b323acad..f79013e396 100644 --- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py +++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py @@ -14,7 +14,7 @@ from xmodule import peer_grading_service from xmodule.x_module import ModuleSystem from mitxmako.shortcuts import render_to_string -log=logging.getLogger(__name__) +log = logging.getLogger(__name__) NOTIFICATION_CACHE_TIME = 300 KEY_PREFIX = "open_ended_" @@ -26,10 +26,11 @@ NOTIFICATION_TYPES = ( ('flagged_submissions_exist', 'open_ended_flagged_problems', 'Flagged Submissions') ) + def staff_grading_notifications(course, user): staff_gs = StaffGradingService(settings.STAFF_GRADING_INTERFACE) - pending_grading=False - img_path= "" + pending_grading = False + img_path = "" course_id = course.id student_id = unique_id_for_user(user) notification_type = "staff" @@ -42,7 +43,7 @@ def staff_grading_notifications(course, user): notifications = json.loads(staff_gs.get_notifications(course_id)) if notifications['success']: if notifications['staff_needs_to_grade']: - pending_grading=True + pending_grading = True except: #Non catastrophic error, so no real action notifications = {} @@ -51,17 +52,18 @@ def staff_grading_notifications(course, user): if pending_grading: img_path = "/static/images/slider-handle.png" - notification_dict = {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} + notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications} set_value_in_cache(student_id, course_id, notification_type, notification_dict) return notification_dict + def peer_grading_notifications(course, user): - system = ModuleSystem(None,None,None,render_to_string,None) + system = ModuleSystem(None, None, None, render_to_string, None) peer_gs = peer_grading_service.PeerGradingService(settings.PEER_GRADING_INTERFACE, system) - pending_grading=False - img_path= "" + pending_grading = False + img_path = "" course_id = course.id student_id = unique_id_for_user(user) notification_type = "peer" @@ -71,10 +73,10 @@ def peer_grading_notifications(course, user): return notification_dict try: - notifications = json.loads(peer_gs.get_notifications(course_id,student_id)) + notifications = json.loads(peer_gs.get_notifications(course_id, student_id)) if notifications['success']: if notifications['student_needs_to_peer_grade']: - pending_grading=True + pending_grading = True except: #Non catastrophic error, so no real action notifications = {} @@ -83,12 +85,13 @@ def peer_grading_notifications(course, user): if pending_grading: img_path = "/static/images/slider-handle.png" - notification_dict = {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} + notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications} set_value_in_cache(student_id, course_id, notification_type, notification_dict) return notification_dict + def combined_notifications(course, user): controller_url = open_ended_util.get_controller_url() controller_qs = ControllerQueryService(controller_url) @@ -102,24 +105,24 @@ def combined_notifications(course, user): return notification_dict min_time_to_query = user.last_login - last_module_seen = StudentModule.objects.filter(student=user, course_id = course_id, modified__gt=min_time_to_query).values('modified').order_by('-modified') + last_module_seen = StudentModule.objects.filter(student=user, course_id=course_id, modified__gt=min_time_to_query).values('modified').order_by('-modified') last_module_seen_count = last_module_seen.count() - if last_module_seen_count>0: + if last_module_seen_count > 0: last_time_viewed = last_module_seen[0]['modified'] - datetime.timedelta(seconds=(NOTIFICATION_CACHE_TIME + 60)) else: last_time_viewed = user.last_login - pending_grading= False + pending_grading = False - img_path= "" + img_path = "" try: - controller_response = controller_qs.check_combined_notifications(course.id,student_id, user_is_staff, last_time_viewed) + controller_response = controller_qs.check_combined_notifications(course.id, student_id, user_is_staff, last_time_viewed) log.debug(controller_response) notifications = json.loads(controller_response) if notifications['success']: if notifications['overall_need_to_check']: - pending_grading=True + pending_grading = True except: #Non catastrophic error, so no real action notifications = {} @@ -128,36 +131,41 @@ def combined_notifications(course, user): if pending_grading: img_path = "/static/images/slider-handle.png" - notification_dict = {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} + notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications} set_value_in_cache(student_id, course_id, notification_type, notification_dict) return notification_dict + def get_value_from_cache(student_id, course_id, notification_type): key_name = create_key_name(student_id, course_id, notification_type) success, value = _get_value_from_cache(key_name) return success, value + def set_value_in_cache(student_id, course_id, notification_type, value): key_name = create_key_name(student_id, course_id, notification_type) _set_value_in_cache(key_name, value) + def create_key_name(student_id, course_id, notification_type): key_name = "{prefix}{type}_{course}_{student}".format(prefix=KEY_PREFIX, type=notification_type, course=course_id, student=student_id) return key_name + def _get_value_from_cache(key_name): value = cache.get(key_name) success = False if value is None: - return success , value + return success, value try: value = json.loads(value) success = True except: pass - return success , value + return success, value + def _set_value_in_cache(key_name, value): - cache.set(key_name, json.dumps(value), NOTIFICATION_CACHE_TIME) \ No newline at end of file + cache.set(key_name, json.dumps(value), NOTIFICATION_CACHE_TIME) diff --git a/lms/djangoapps/open_ended_grading/open_ended_util.py b/lms/djangoapps/open_ended_grading/open_ended_util.py index 07744d7d2c..1aa0f1ba70 100644 --- a/lms/djangoapps/open_ended_grading/open_ended_util.py +++ b/lms/djangoapps/open_ended_grading/open_ended_util.py @@ -1,12 +1,13 @@ from django.conf import settings import logging -log=logging.getLogger(__name__) +log = logging.getLogger(__name__) + def get_controller_url(): peer_grading_url = settings.PEER_GRADING_INTERFACE['url'] split_url = peer_grading_url.split("/") controller_url = "http://" + split_url[2] + "/grading_controller" - controller_settings=settings.PEER_GRADING_INTERFACE.copy() + controller_settings = settings.PEER_GRADING_INTERFACE.copy() controller_settings['url'] = controller_url return controller_settings diff --git a/lms/djangoapps/open_ended_grading/staff_grading.py b/lms/djangoapps/open_ended_grading/staff_grading.py index 7a48b25a49..e39b26da56 100644 --- a/lms/djangoapps/open_ended_grading/staff_grading.py +++ b/lms/djangoapps/open_ended_grading/staff_grading.py @@ -22,4 +22,3 @@ class StaffGrading(object): return "Instructor grading!" # context = {} # return render_to_string('courseware/instructor_grading_view.html', context) - diff --git a/lms/djangoapps/open_ended_grading/staff_grading_service.py b/lms/djangoapps/open_ended_grading/staff_grading_service.py index d8bee99ac7..dfadacb724 100644 --- a/lms/djangoapps/open_ended_grading/staff_grading_service.py +++ b/lms/djangoapps/open_ended_grading/staff_grading_service.py @@ -21,6 +21,7 @@ from mitxmako.shortcuts import render_to_string log = logging.getLogger(__name__) + class MockStaffGradingService(object): """ A simple mockup of a staff grading service, testing. @@ -28,7 +29,7 @@ class MockStaffGradingService(object): def __init__(self): self.cnt = 0 - def get_next(self,course_id, location, grader_id): + def get_next(self, course_id, location, grader_id): self.cnt += 1 return json.dumps({'success': True, 'submission_id': self.cnt, @@ -61,7 +62,7 @@ class StaffGradingService(GradingService): Interface to staff grading backend. """ def __init__(self, config): - config['system'] = ModuleSystem(None,None,None,render_to_string,None) + config['system'] = ModuleSystem(None, None, None, render_to_string, None) super(StaffGradingService, self).__init__(config) self.get_next_url = self.url + '/get_next_submission/' self.save_grade_url = self.url + '/save_grade/' @@ -85,7 +86,7 @@ class StaffGradingService(GradingService): Raises: GradingServiceError: something went wrong with the connection. """ - params = {'course_id': course_id,'grader_id': grader_id} + params = {'course_id': course_id, 'grader_id': grader_id} return self.get(self.get_problem_list_url, params) @@ -166,6 +167,7 @@ def staff_grading_service(): return _service + def _err_response(msg): """ Return a HttpResponse with a json dump with success=False, and the given error message. @@ -329,4 +331,3 @@ def save_grade(request, course_id): # Ok, save_grade seemed to work. Get the next submission to grade. return HttpResponse(_get_next(course_id, grader_id, location), mimetype="application/json") - diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py index 3ee8352c5c..4d220d4baa 100644 --- a/lms/djangoapps/open_ended_grading/tests.py +++ b/lms/djangoapps/open_ended_grading/tests.py @@ -44,7 +44,7 @@ class TestStaffGradingService(ct.PageLoader): self.create_account('u2', self.instructor, self.password) self.activate_user(self.student) self.activate_user(self.instructor) - + self.course_id = "edX/toy/2012_Fall" self.toy = modulestore().get_course(self.course_id) def make_instructor(course): @@ -118,7 +118,7 @@ class TestStaffGradingService(ct.PageLoader): self.assertTrue(d['success'], str(d)) self.assertIsNotNone(d['problem_list']) - + @override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE) class TestPeerGradingService(ct.PageLoader): ''' @@ -137,7 +137,7 @@ class TestPeerGradingService(ct.PageLoader): self.create_account('u2', self.instructor, self.password) self.activate_user(self.student) self.activate_user(self.instructor) - + self.course_id = "edX/toy/2012_Fall" self.toy = modulestore().get_course(self.course_id) location = "i4x://edX/toy/peergrading/init" @@ -146,7 +146,7 @@ class TestPeerGradingService(ct.PageLoader): self.system = ModuleSystem(location, None, None, render_to_string, None) self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system) - self.peer_module = peer_grading_module.PeerGradingModule(self.system,location,"",self.descriptor) + self.peer_module = peer_grading_module.PeerGradingModule(self.system, location, "", self.descriptor) self.peer_module.peer_gs = self.mock_service self.logout() @@ -171,7 +171,7 @@ class TestPeerGradingService(ct.PageLoader): def test_save_grade_success(self): raise SkipTest() data = 'rubric_scores[]=1|rubric_scores[]=2|location=' + self.location + '|submission_id=1|submission_key=fake key|score=2|feedback=feedback|submission_flagged=False' - qdict = QueryDict(data.replace("|","&")) + qdict = QueryDict(data.replace("|", "&")) r = self.peer_module.save_grade(qdict) d = r self.assertTrue(d['success']) @@ -222,7 +222,7 @@ class TestPeerGradingService(ct.PageLoader): def test_save_calibration_essay_success(self): raise SkipTest() data = 'rubric_scores[]=1|rubric_scores[]=2|location=' + self.location + '|submission_id=1|submission_key=fake key|score=2|feedback=feedback|submission_flagged=False' - qdict = QueryDict(data.replace("|","&")) + qdict = QueryDict(data.replace("|", "&")) r = self.peer_module.save_calibration_essay(qdict) d = r self.assertTrue(d['success']) @@ -235,4 +235,3 @@ class TestPeerGradingService(ct.PageLoader): self.assertFalse(d['success']) self.assertTrue(d['error'].find('Missing required keys:') > -1) self.assertFalse('actual_score' in d) - diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index af7f930207..f2e2a4513e 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -10,7 +10,7 @@ from mitxmako.shortcuts import render_to_response from django.core.urlresolvers import reverse from student.models import unique_id_for_user -from courseware.courses import get_course_with_access +from courseware.courses import get_course_with_access from controller_query_service import ControllerQueryService from xmodule.grading_service_module import GradingServiceError @@ -38,12 +38,15 @@ Reverses the URL from the name and the course id, and then adds a trailing slash it does not exist yet """ + + def _reverse_with_slash(url_name, course_id): ajax_url = _reverse_without_slash(url_name, course_id) if not ajax_url.endswith('/'): ajax_url += '/' return ajax_url + def _reverse_without_slash(url_name, course_id): ajax_url = reverse(url_name, kwargs={'course_id': course_id}) return ajax_url @@ -52,14 +55,16 @@ DESCRIPTION_DICT = { 'Peer Grading': "View all problems that require peer assessment in this particular course.", 'Staff Grading': "View ungraded submissions submitted by students for the open ended problems in the course.", 'Problems you have submitted': "View open ended problems that you have previously submitted for grading.", - 'Flagged Submissions' : "View submissions that have been flagged by students as inappropriate." + 'Flagged Submissions': "View submissions that have been flagged by students as inappropriate." } ALERT_DICT = { 'Peer Grading': "New submissions to grade", 'Staff Grading': "New submissions to grade", 'Problems you have submitted': "New grades have been returned", - 'Flagged Submissions' : "Submissions have been flagged for review" + 'Flagged Submissions': "Submissions have been flagged for review" } + + @cache_control(no_cache=True, no_store=True, must_revalidate=True) def staff_grading(request, course_id): """ @@ -68,7 +73,7 @@ def staff_grading(request, course_id): course = get_course_with_access(request.user, course_id, 'staff') ajax_url = _reverse_with_slash('staff_grading', course_id) - + return render_to_response('instructor/staff_grading.html', { 'course': course, 'course_id': course_id, @@ -76,6 +81,7 @@ def staff_grading(request, course_id): # Checked above 'staff_access': True, }) + @cache_control(no_cache=True, no_store=True, must_revalidate=True) def peer_grading(request, course_id): ''' @@ -98,6 +104,7 @@ def peer_grading(request, course_id): log.exception(error_message + "Current course is: {0}".format(course_id)) return HttpResponse(error_message) + def generate_problem_url(problem_url_parts, base_course_url): """ From a list of problem url parts generated by search.path_to_location and a base course url, generates a url to a problem @@ -106,10 +113,10 @@ def generate_problem_url(problem_url_parts, base_course_url): @return: A path to the problem """ problem_url = base_course_url + "/" - for z in xrange(0,len(problem_url_parts)): + for z in xrange(0, len(problem_url_parts)): part = problem_url_parts[z] if part is not None: - if z==1: + if z == 1: problem_url += "courseware/" problem_url += part + "/" return problem_url @@ -139,10 +146,10 @@ def student_problem_list(request, course_id): else: problem_list = problem_list_dict['problem_list'] - for i in xrange(0,len(problem_list)): + for i in xrange(0, len(problem_list)): problem_url_parts = search.path_to_location(modulestore(), course.id, problem_list[i]['location']) problem_url = generate_problem_url(problem_url_parts, base_course_url) - problem_list[i].update({'actual_url' : problem_url}) + problem_list[i].update({'actual_url': problem_url}) """ except GradingServiceError: @@ -166,6 +173,7 @@ def student_problem_list(request, course_id): # Checked above 'staff_access': False, }) + @cache_control(no_cache=True, no_store=True, must_revalidate=True) def flagged_problem_list(request, course_id): ''' @@ -186,7 +194,7 @@ def flagged_problem_list(request, course_id): success = problem_list_dict['success'] if 'error' in problem_list_dict: error_text = problem_list_dict['error'] - problem_list=[] + problem_list = [] else: problem_list = problem_list_dict['flagged_submissions'] @@ -211,6 +219,7 @@ def flagged_problem_list(request, course_id): } return render_to_response('open_ended_problems/open_ended_flagged_problems.html', context) + @cache_control(no_cache=True, no_store=True, must_revalidate=True) def combined_notifications(request, course_id): """ @@ -220,11 +229,11 @@ def combined_notifications(request, course_id): user = request.user notifications = open_ended_notifications.combined_notifications(course, user) response = notifications['response'] - notification_tuples=open_ended_notifications.NOTIFICATION_TYPES + notification_tuples = open_ended_notifications.NOTIFICATION_TYPES notification_list = [] - for response_num in xrange(0,len(notification_tuples)): - tag=notification_tuples[response_num][0] + for response_num in xrange(0, len(notification_tuples)): + tag = notification_tuples[response_num][0] if tag in response: url_name = notification_tuples[response_num][1] human_name = notification_tuples[response_num][2] @@ -241,11 +250,11 @@ def combined_notifications(request, course_id): alert_message = ALERT_DICT[human_name] else: alert_message = "" - + notification_item = { - 'url' : url, - 'name' : human_name, - 'alert' : has_img, + 'url': url, + 'name': human_name, + 'alert': has_img, 'description': description, 'alert_message': alert_message } @@ -253,17 +262,18 @@ def combined_notifications(request, course_id): ajax_url = _reverse_with_slash('open_ended_notifications', course_id) combined_dict = { - 'error_text' : "", - 'notification_list' : notification_list, - 'course' : course, - 'success' : True, - 'ajax_url' : ajax_url, + 'error_text': "", + 'notification_list': notification_list, + 'course': course, + 'success': True, + 'ajax_url': ajax_url, } return render_to_response('open_ended_problems/combined_notifications.html', combined_dict ) + @cache_control(no_cache=True, no_store=True, must_revalidate=True) def take_action_on_flags(request, course_id): """ @@ -293,5 +303,3 @@ def take_action_on_flags(request, course_id): except GradingServiceError: log.exception("Error saving calibration grade, submission_id: {0}, submission_key: {1}, grader_id: {2}".format(submission_id, submission_key, grader_id)) return _err_response('Could not connect to grading service') - - diff --git a/lms/djangoapps/portal/features/common.py b/lms/djangoapps/portal/features/common.py index 20c2ab56b8..8bfb548367 100644 --- a/lms/djangoapps/portal/features/common.py +++ b/lms/djangoapps/portal/features/common.py @@ -1,4 +1,4 @@ -from lettuce import world, step#, before, after +from lettuce import world, step # , before, after from factories import * from django.core.management import call_command from nose.tools import assert_equals, assert_in @@ -11,74 +11,90 @@ import time from logging import getLogger logger = getLogger(__name__) + @step(u'I wait (?:for )?"(\d+)" seconds?$') def wait(step, seconds): time.sleep(float(seconds)) + @step('I (?:visit|access|open) the homepage$') def i_visit_the_homepage(step): world.browser.visit(django_url('/')) assert world.browser.is_element_present_by_css('header.global', 10) + @step(u'I (?:visit|access|open) the dashboard$') def i_visit_the_dashboard(step): world.browser.visit(django_url('/dashboard')) assert world.browser.is_element_present_by_css('section.container.dashboard', 5) + @step(r'click (?:the|a) link (?:called|with the text) "([^"]*)"$') def click_the_link_called(step, text): world.browser.find_link_by_text(text).click() + @step('I should be on the dashboard page$') def i_should_be_on_the_dashboard(step): assert world.browser.is_element_present_by_css('section.container.dashboard', 5) assert world.browser.title == 'Dashboard' + @step(u'I (?:visit|access|open) the courses page$') def i_am_on_the_courses_page(step): world.browser.visit(django_url('/courses')) assert world.browser.is_element_present_by_css('section.courses') + @step('I should see that the path is "([^"]*)"$') def i_should_see_that_the_path_is(step, path): assert world.browser.url == django_url(path) + @step(u'the page title should be "([^"]*)"$') def the_page_title_should_be(step, title): assert world.browser.title == title + @step(r'should see that the url is "([^"]*)"$') def should_have_the_url(step, url): assert_equals(world.browser.url, url) + @step(r'should see (?:the|a) link (?:called|with the text) "([^"]*)"$') def should_see_a_link_called(step, text): assert len(world.browser.find_link_by_text(text)) > 0 + @step(r'should see "(.*)" (?:somewhere|anywhere) in (?:the|this) page') def should_see_in_the_page(step, text): assert_in(text, world.browser.html) + @step('I am logged in$') def i_am_logged_in(step): world.create_user('robot') world.log_in('robot@edx.org', 'test') + @step('I am not logged in$') def i_am_not_logged_in(step): world.browser.cookies.delete() + @step(u'I am registered for a course$') def i_am_registered_for_a_course(step): world.create_user('robot') u = User.objects.get(username='robot') CourseEnrollment.objects.create(user=u, course_id='MITx/6.002x/2012_Fall') world.log_in('robot@edx.org', 'test') - + + @step(u'I am an edX user$') def i_am_an_edx_user(step): world.create_user('robot') + @step(u'User "([^"]*)" is an edX user$') def registered_edx_user(step, uname): world.create_user(uname) diff --git a/lms/djangoapps/portal/features/factories.py b/lms/djangoapps/portal/features/factories.py index 07b615f468..71781ea3d6 100644 --- a/lms/djangoapps/portal/features/factories.py +++ b/lms/djangoapps/portal/features/factories.py @@ -3,6 +3,7 @@ from student.models import User, UserProfile, Registration from datetime import datetime import uuid + class UserProfileFactory(factory.Factory): FACTORY_FOR = UserProfile @@ -13,12 +14,14 @@ class UserProfileFactory(factory.Factory): mailing_address = None goals = 'World domination' + class RegistrationFactory(factory.Factory): FACTORY_FOR = Registration user = None activation_key = uuid.uuid4().hex + class UserFactory(factory.Factory): FACTORY_FOR = User diff --git a/lms/djangoapps/portal/features/homepage.py b/lms/djangoapps/portal/features/homepage.py index 638d65077c..442098c161 100644 --- a/lms/djangoapps/portal/features/homepage.py +++ b/lms/djangoapps/portal/features/homepage.py @@ -1,6 +1,7 @@ from lettuce import world, step from nose.tools import assert_in + @step('I should see "([^"]*)" in the Partners section$') def i_should_see_partner(step, partner): partners = world.browser.find_by_css(".partner .name span") diff --git a/lms/djangoapps/portal/features/login.py b/lms/djangoapps/portal/features/login.py index 5f200eb259..094db078ca 100644 --- a/lms/djangoapps/portal/features/login.py +++ b/lms/djangoapps/portal/features/login.py @@ -1,26 +1,31 @@ from lettuce import step, world from django.contrib.auth.models import User + @step('I am an unactivated user$') def i_am_an_unactivated_user(step): user_is_an_unactivated_user('robot') + @step('I am an activated user$') def i_am_an_activated_user(step): user_is_an_activated_user('robot') + @step('I submit my credentials on the login form') def i_submit_my_credentials_on_the_login_form(step): fill_in_the_login_form('email', 'robot@edx.org') fill_in_the_login_form('password', 'test') login_form = world.browser.find_by_css('form#login_form') login_form.find_by_value('Access My Courses').click() - + + @step(u'I should see the login error message "([^"]*)"$') def i_should_see_the_login_error_message(step, msg): login_error_div = world.browser.find_by_css('form#login_form #login_error') assert (msg in login_error_div.text) + @step(u'click the dropdown arrow$') def click_the_dropdown(step): css = ".dropdown" @@ -29,16 +34,19 @@ def click_the_dropdown(step): #### helper functions + def user_is_an_unactivated_user(uname): u = User.objects.get(username=uname) u.is_active = False u.save() + def user_is_an_activated_user(uname): u = User.objects.get(username=uname) u.is_active = True u.save() + def fill_in_the_login_form(field, value): login_form = world.browser.find_by_css('form#login_form') form_field = login_form.find_by_name(field) diff --git a/lms/djangoapps/portal/features/registration.py b/lms/djangoapps/portal/features/registration.py index 124bed4923..b2b4c4bd8d 100644 --- a/lms/djangoapps/portal/features/registration.py +++ b/lms/djangoapps/portal/features/registration.py @@ -1,5 +1,6 @@ from lettuce import world, step + @step('I register for the course numbered "([^"]*)"$') def i_register_for_the_course(step, course): courses_section = world.browser.find_by_css('section.courses') @@ -13,11 +14,13 @@ def i_register_for_the_course(step, course): assert world.browser.is_element_present_by_css('section.container.dashboard') + @step(u'I should see the course numbered "([^"]*)" in my dashboard$') def i_should_see_that_course_in_my_dashboard(step, course): course_link_css = 'section.my-courses a[href*="%s"]' % course assert world.browser.is_element_present_by_css(course_link_css) + @step(u'I press the "([^"]*)" button in the Unenroll dialog') def i_press_the_button_in_the_unenroll_dialog(step, value): button_css = 'section#unenroll-modal input[value="%s"]' % value diff --git a/lms/djangoapps/portal/features/signup.py b/lms/djangoapps/portal/features/signup.py index afde72b589..3a697a6102 100644 --- a/lms/djangoapps/portal/features/signup.py +++ b/lms/djangoapps/portal/features/signup.py @@ -1,22 +1,25 @@ from lettuce import world, step + @step('I fill in "([^"]*)" on the registration form with "([^"]*)"$') def when_i_fill_in_field_on_the_registration_form_with_value(step, field, value): register_form = world.browser.find_by_css('form#register_form') form_field = register_form.find_by_name(field) form_field.fill(value) + @step('I press the "([^"]*)" button on the registration form$') def i_press_the_button_on_the_registration_form(step, button): register_form = world.browser.find_by_css('form#register_form') register_form.find_by_value(button).click() + @step('I check the checkbox named "([^"]*)"$') def i_check_checkbox(step, checkbox): world.browser.find_by_name(checkbox).check() + @step('I should see "([^"]*)" in the dashboard banner$') def i_should_see_text_in_the_dashboard_banner_section(step, text): css_selector = "section.dashboard-banner h2" assert (text in world.browser.find_by_css(css_selector).text) - \ No newline at end of file diff --git a/lms/djangoapps/psychometrics/management/commands/init_psychometrics.py b/lms/djangoapps/psychometrics/management/commands/init_psychometrics.py index 5e782df595..53f6e17e9d 100644 --- a/lms/djangoapps/psychometrics/management/commands/init_psychometrics.py +++ b/lms/djangoapps/psychometrics/management/commands/init_psychometrics.py @@ -2,7 +2,9 @@ # # generate pyschometrics data from tracking logs and student module data -import os, sys, string +import os +import sys +import string import datetime import json @@ -17,7 +19,7 @@ from django.core.management.base import BaseCommand #db = "ocwtutor" # for debugging #db = "default" -db = getattr(settings,'DATABASE_FOR_PSYCHOMETRICS','default') +db = getattr(settings, 'DATABASE_FOR_PSYCHOMETRICS', 'default') class Command(BaseCommand): @@ -32,39 +34,39 @@ class Command(BaseCommand): #PsychometricData.objects.using(db).all().delete() smset = StudentModule.objects.using(db).exclude(max_grade=None) - + for sm in smset: url = sm.module_state_key location = Location(url) - if not location.category=="problem": + if not location.category == "problem": continue try: state = json.loads(sm.state) done = state['done'] except: - print "Oops, failed to eval state for %s (state=%s)" % (sm,sm.state) + print "Oops, failed to eval state for %s (state=%s)" % (sm, sm.state) continue - + if done: # only keep if problem completed try: pmd = PsychometricData.objects.using(db).get(studentmodule=sm) except PsychometricData.DoesNotExist: pmd = PsychometricData(studentmodule=sm) - + pmd.done = done pmd.attempts = state['attempts'] - + # get attempt times from tracking log uname = sm.student.username - tset = TrackingLog.objects.using(db).filter(username=uname, event_type__contains='save_problem_check') + tset = TrackingLog.objects.using(db).filter(username=uname, event_type__contains='save_problem_check') tset = tset.filter(event_source='server') tset = tset.filter(event__contains="'%s'" % url) checktimes = [x.dtcreated for x in tset] pmd.checktimes = checktimes - if not len(checktimes)==pmd.attempts: + if not len(checktimes) == pmd.attempts: print "Oops, mismatch in number of attempts and check times for %s" % pmd - + #print pmd pmd.save(using=db) - + print "%d PMD entries" % PsychometricData.objects.using(db).all().count() diff --git a/lms/djangoapps/psychometrics/models.py b/lms/djangoapps/psychometrics/models.py index 4ffdf59120..60455f01b8 100644 --- a/lms/djangoapps/psychometrics/models.py +++ b/lms/djangoapps/psychometrics/models.py @@ -7,6 +7,7 @@ from django.db import models from courseware.models import StudentModule + class PsychometricData(models.Model): """ This data is a table linking student, module, and module performance, @@ -25,7 +26,7 @@ class PsychometricData(models.Model): done = models.BooleanField(default=False) attempts = models.IntegerField(default=0) # extracted from studentmodule.state - checktimes = models.TextField(null=True, blank=True) # internally stored as list of datetime objects + checktimes = models.TextField(null=True, blank=True) # internally stored as list of datetime objects # keep in mind # grade = studentmodule.grade @@ -33,7 +34,7 @@ class PsychometricData(models.Model): # student = studentmodule.student # course_id = studentmodule.course_id # location = studentmodule.module_state_key - + def __unicode__(self): sm = self.studentmodule return "[PsychometricData] %s url=%s, grade=%s, max=%s, attempts=%s, ct=%s" % (sm.student, @@ -42,4 +43,3 @@ class PsychometricData(models.Model): sm.max_grade, self.attempts, self.checktimes) - diff --git a/lms/djangoapps/psychometrics/psychoanalyze.py b/lms/djangoapps/psychometrics/psychoanalyze.py index dd7d328278..28a5c4437c 100644 --- a/lms/djangoapps/psychometrics/psychoanalyze.py +++ b/lms/djangoapps/psychometrics/psychoanalyze.py @@ -66,7 +66,7 @@ class StatVar(object): if x > self.max: self.max = x self.sum += x - self.sum2 += x**2 + self.sum2 += x ** 2 self.cnt += 1 def avg(self): @@ -77,11 +77,11 @@ class StatVar(object): def var(self): if self.cnt is None: return 0 - return (self.sum2 / 1.0 / self.cnt / (self.unit**2)) - (self.avg()**2) + return (self.sum2 / 1.0 / self.cnt / (self.unit ** 2)) - (self.avg() ** 2) def sdv(self): v = self.var() - if v>0: + if v > 0: return math.sqrt(v) else: return 0 @@ -112,7 +112,7 @@ def make_histogram(ydata, bins=None): hist = dict(zip(bins, [0] * nbins)) for y in ydata: for b in bins[::-1]: # in reverse order - if y>b: + if y > b: hist[b] += 1 break # hist['bins'] = bins @@ -128,7 +128,7 @@ def problems_with_psychometric_data(course_id): ''' pmdset = PsychometricData.objects.using(db).filter(studentmodule__course_id=course_id) plist = [p['studentmodule__module_state_key'] for p in pmdset.values('studentmodule__module_state_key').distinct()] - problems = dict( (p, pmdset.filter(studentmodule__module_state_key=p).count()) for p in plist ) + problems = dict((p, pmdset.filter(studentmodule__module_state_key=p).count()) for p in plist) return problems @@ -241,7 +241,7 @@ def generate_plots_for_problem(problem): ylast = 0 for x in xdat: y = gset.filter(attempts=x).count() / ngset - ydat.append( y + ylast ) + ydat.append(y + ylast) ylast = y + ylast yset['ydat'] = ydat diff --git a/lms/djangoapps/simplewiki/mdx_mathjax.py b/lms/djangoapps/simplewiki/mdx_mathjax.py index a9148511e3..b14803744b 100644 --- a/lms/djangoapps/simplewiki/mdx_mathjax.py +++ b/lms/djangoapps/simplewiki/mdx_mathjax.py @@ -28,4 +28,3 @@ class MathJaxExtension(markdown.Extension): def makeExtension(configs=None): return MathJaxExtension(configs) - diff --git a/lms/djangoapps/simplewiki/views.py b/lms/djangoapps/simplewiki/views.py index ef0928709f..38c367dec9 100644 --- a/lms/djangoapps/simplewiki/views.py +++ b/lms/djangoapps/simplewiki/views.py @@ -55,6 +55,7 @@ def update_template_dictionary(dictionary, request=None, course=None, article=No else: dictionary['staff_access'] = False + def view(request, article_path, course_id=None): course = get_opt_course_with_access(request.user, course_id, 'load') diff --git a/lms/djangoapps/static_template_view/views.py b/lms/djangoapps/static_template_view/views.py index 8ab6216eda..022f12b148 100644 --- a/lms/djangoapps/static_template_view/views.py +++ b/lms/djangoapps/static_template_view/views.py @@ -46,4 +46,3 @@ def render_404(request): def render_500(request): return HttpResponseServerError(render_to_string('static_templates/server-error.html', {})) - diff --git a/lms/djangoapps/staticbook/views.py b/lms/djangoapps/staticbook/views.py index fabb8b861c..6750d151ce 100644 --- a/lms/djangoapps/staticbook/views.py +++ b/lms/djangoapps/staticbook/views.py @@ -6,6 +6,7 @@ from courseware.access import has_access from courseware.courses import get_course_with_access from lxml import etree + @login_required def index(request, course_id, book_index, page=None): course = get_course_with_access(request.user, course_id, 'load') @@ -22,9 +23,10 @@ def index(request, course_id, book_index, page=None): {'book_index': book_index, 'page': int(page), 'course': course, 'book_url': textbook.book_url, 'table_of_contents': table_of_contents, - 'start_page' : textbook.start_page, - 'end_page' : textbook.end_page, + 'start_page': textbook.start_page, + 'end_page': textbook.end_page, 'staff_access': staff_access}) + def index_shifted(request, course_id, page): return index(request, course_id=course_id, page=int(page) + 24) diff --git a/lms/djangoapps/terrain/__init__.py b/lms/djangoapps/terrain/__init__.py index dd6869e7fd..3445a01d17 100644 --- a/lms/djangoapps/terrain/__init__.py +++ b/lms/djangoapps/terrain/__init__.py @@ -3,4 +3,4 @@ # See https://groups.google.com/forum/?fromgroups=#!msg/lettuce-users/5VyU9B4HcX8/USgbGIJdS5QJ from terrain.browser import * from terrain.steps import * -from terrain.factories import * \ No newline at end of file +from terrain.factories import * diff --git a/lms/djangoapps/terrain/browser.py b/lms/djangoapps/terrain/browser.py index 1c2d401680..e1925bde0b 100644 --- a/lms/djangoapps/terrain/browser.py +++ b/lms/djangoapps/terrain/browser.py @@ -8,20 +8,23 @@ logger.info("Loading the lettuce acceptance testing terrain file...") from django.core.management import call_command + @before.harvest def initial_setup(server): # Launch firefox world.browser = Browser('chrome') + @before.each_scenario def reset_data(scenario): - # Clean out the django test database defined in the + # Clean out the django test database defined in the # envs/acceptance.py file: mitx_all/db/test_mitx.db logger.debug("Flushing the test database...") call_command('flush', interactive=False) + @after.all def teardown_browser(total): # Quit firefox world.browser.quit() - pass \ No newline at end of file + pass diff --git a/lms/djangoapps/terrain/factories.py b/lms/djangoapps/terrain/factories.py index 377ce54d56..896f115df5 100644 --- a/lms/djangoapps/terrain/factories.py +++ b/lms/djangoapps/terrain/factories.py @@ -7,6 +7,7 @@ from time import gmtime from uuid import uuid4 from xmodule.timeparse import stringify_time + class UserProfileFactory(Factory): FACTORY_FOR = UserProfile @@ -17,12 +18,14 @@ class UserProfileFactory(Factory): mailing_address = None goals = 'World domination' + class RegistrationFactory(Factory): FACTORY_FOR = Registration user = None activation_key = uuid4().hex + class UserFactory(Factory): FACTORY_FOR = User @@ -37,12 +40,15 @@ class UserFactory(Factory): last_login = datetime(2012, 1, 1) date_joined = datetime(2011, 1, 1) -def XMODULE_COURSE_CREATION(class_to_create, **kwargs): + +def XMODULE_COURSE_CREATION(class_to_create, **kwargs): return XModuleCourseFactory._create(class_to_create, **kwargs) + def XMODULE_ITEM_CREATION(class_to_create, **kwargs): return XModuleItemFactory._create(class_to_create, **kwargs) + class XModuleCourseFactory(Factory): """ Factory for XModule courses. @@ -58,7 +64,7 @@ class XModuleCourseFactory(Factory): org = kwargs.get('org') number = kwargs.get('number') display_name = kwargs.get('display_name') - location = Location('i4x', org, number, + location = Location('i4x', org, number, 'course', Location.clean(display_name)) store = modulestore('direct') @@ -72,20 +78,22 @@ class XModuleCourseFactory(Factory): new_course.metadata['data_dir'] = uuid4().hex new_course.metadata['start'] = stringify_time(gmtime()) - new_course.tabs = [{"type": "courseware"}, + new_course.tabs = [{"type": "courseware"}, {"type": "course_info", "name": "Course Info"}, {"type": "discussion", "name": "Discussion"}, {"type": "wiki", "name": "Wiki"}, {"type": "progress", "name": "Progress"}] # Update the data in the mongo datastore - store.update_metadata(new_course.location.url(), new_course.own_metadata) + store.update_metadata(new_course.location.url(), new_course.own_metadata) return new_course + class Course: pass + class CourseFactory(XModuleCourseFactory): FACTORY_FOR = Course @@ -94,6 +102,7 @@ class CourseFactory(XModuleCourseFactory): number = '999' display_name = 'Robot Super Course' + class XModuleItemFactory(Factory): """ Factory for XModule items. @@ -110,7 +119,7 @@ class XModuleItemFactory(Factory): """ DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info'] - + parent_location = Location(kwargs.get('parent_location')) template = Location(kwargs.get('template')) display_name = kwargs.get('display_name') @@ -137,12 +146,14 @@ class XModuleItemFactory(Factory): return new_item + class Item: pass + class ItemFactory(XModuleItemFactory): FACTORY_FOR = Item parent_location = 'i4x://MITx/999/course/Robot_Super_Course' template = 'i4x://edx/templates/chapter/Empty' - display_name = 'Section One' \ No newline at end of file + display_name = 'Section One' diff --git a/lms/djangoapps/terrain/steps.py b/lms/djangoapps/terrain/steps.py index 6824fa16ce..6b2a813d8d 100644 --- a/lms/djangoapps/terrain/steps.py +++ b/lms/djangoapps/terrain/steps.py @@ -15,79 +15,97 @@ import os.path from logging import getLogger logger = getLogger(__name__) + @step(u'I wait (?:for )?"(\d+)" seconds?$') def wait(step, seconds): time.sleep(float(seconds)) + @step('I (?:visit|access|open) the homepage$') def i_visit_the_homepage(step): world.browser.visit(django_url('/')) assert world.browser.is_element_present_by_css('header.global', 10) + @step(u'I (?:visit|access|open) the dashboard$') def i_visit_the_dashboard(step): world.browser.visit(django_url('/dashboard')) assert world.browser.is_element_present_by_css('section.container.dashboard', 5) + @step('I should be on the dashboard page$') def i_should_be_on_the_dashboard(step): assert world.browser.is_element_present_by_css('section.container.dashboard', 5) assert world.browser.title == 'Dashboard' + @step(u'I (?:visit|access|open) the courses page$') def i_am_on_the_courses_page(step): world.browser.visit(django_url('/courses')) assert world.browser.is_element_present_by_css('section.courses') + @step(u'I press the "([^"]*)" button$') def and_i_press_the_button(step, value): button_css = 'input[value="%s"]' % value world.browser.find_by_css(button_css).first.click() + @step(u'I click the link with the text "([^"]*)"$') def click_the_link_with_the_text_group1(step, linktext): world.browser.find_link_by_text(linktext).first.click() + @step('I should see that the path is "([^"]*)"$') def i_should_see_that_the_path_is(step, path): assert world.browser.url == django_url(path) + @step(u'the page title should be "([^"]*)"$') def the_page_title_should_be(step, title): assert_equals(world.browser.title, title) + @step('I am a logged in user$') def i_am_logged_in_user(step): create_user('robot') - log_in('robot@edx.org','test') + log_in('robot@edx.org', 'test') + @step('I am not logged in$') def i_am_not_logged_in(step): world.browser.cookies.delete() + @step('I am registered for a course$') def i_am_registered_for_a_course(step): create_user('robot') u = User.objects.get(username='robot') CourseEnrollment.objects.get_or_create(user=u, course_id='MITx/6.002x/2012_Fall') + @step('I am registered for course "([^"]*)"$') def i_am_registered_for_course_by_id(step, course_id): register_by_course_id(course_id) + @step('I am staff for course "([^"]*)"$') def i_am_staff_for_course_by_id(step, course_id): register_by_course_id(course_id, True) + @step('I log in$') def i_log_in(step): - log_in('robot@edx.org','test') + log_in('robot@edx.org', 'test') + @step(u'I am an edX user$') def i_am_an_edx_user(step): create_user('robot') #### helper functions + + @world.absorb def create_user(uname): portal_user = UserFactory.build(username=uname, email=uname + '@edx.org') @@ -100,11 +118,12 @@ def create_user(uname): user_profile = UserProfileFactory(user=portal_user) + @world.absorb -def log_in(email, password): +def log_in(email, password): world.browser.cookies.delete() world.browser.visit(django_url('/')) - world.browser.is_element_present_by_css('header.global', 10) + world.browser.is_element_present_by_css('header.global', 10) world.browser.click_link_by_href('#login-modal') login_form = world.browser.find_by_css('form#login_form') login_form.find_by_name('email').fill(email) @@ -114,15 +133,17 @@ def log_in(email, password): # wait for the page to redraw assert world.browser.is_element_present_by_css('.content-wrapper', 10) + @world.absorb def register_by_course_id(course_id, is_staff=False): create_user('robot') u = User.objects.get(username='robot') if is_staff: - u.is_staff=True - u.save() + u.is_staff = True + u.save() CourseEnrollment.objects.get_or_create(user=u, course_id=course_id) + @world.absorb def save_the_html(path='/tmp'): u = world.browser.url @@ -132,6 +153,7 @@ def save_the_html(path='/tmp'): f.write(html) f.close + @world.absorb def save_the_course_content(path='/tmp'): html = world.browser.html.encode('ascii', 'ignore') @@ -140,12 +162,12 @@ def save_the_course_content(path='/tmp'): # get rid of the header, we only want to compare the body soup.head.decompose() - # for now, remove the data-id attributes, because they are + # for now, remove the data-id attributes, because they are # causing mismatches between cms-master and master for item in soup.find_all(attrs={'data-id': re.compile('.*')}): del item['data-id'] - # we also need to remove them from unrendered problems, + # we also need to remove them from unrendered problems, # where they are contained in the text of divs instead of # in attributes of tags # Be careful of whether or not it was the last attribute @@ -164,7 +186,7 @@ def save_the_course_content(path='/tmp'): # use string slicing to grab everything after 'courseware/' in the URL u = world.browser.url - section_url = u[u.find('courseware/')+11:] + section_url = u[u.find('courseware/') + 11:] if not os.path.exists(path): os.makedirs(path) diff --git a/lms/envs/acceptance.py b/lms/envs/acceptance.py index e0857a4392..412815a402 100644 --- a/lms/envs/acceptance.py +++ b/lms/envs/acceptance.py @@ -1,5 +1,5 @@ """ -This config file extends the test environment configuration +This config file extends the test environment configuration so that we can run the lettuce acceptance tests. """ from .test import * @@ -21,14 +21,14 @@ MODULESTORE = { } } -# Set this up so that rake lms[acceptance] and running the +# Set this up so that rake lms[acceptance] and running the # harvest command both use the same (test) database # which they can flush without messing up your dev db DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ENV_ROOT / "db" / "test_mitx.db", - 'TEST_NAME': ENV_ROOT / "db" / "test_mitx.db", + 'TEST_NAME': ENV_ROOT / "db" / "test_mitx.db", } } diff --git a/lms/envs/common.py b/lms/envs/common.py index 614c68b361..10947a9735 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -40,11 +40,11 @@ DISCUSSION_SETTINGS = { # Features MITX_FEATURES = { - 'SAMPLE' : False, - 'USE_DJANGO_PIPELINE' : True, - 'DISPLAY_HISTOGRAMS_TO_STAFF' : True, - 'REROUTE_ACTIVATION_EMAIL' : False, # nonempty string = address for all activation emails - 'DEBUG_LEVEL' : 0, # 0 = lowest level, least verbose, 255 = max level, most verbose + 'SAMPLE': False, + 'USE_DJANGO_PIPELINE': True, + 'DISPLAY_HISTOGRAMS_TO_STAFF': True, + 'REROUTE_ACTIVATION_EMAIL': False, # nonempty string = address for all activation emails + 'DEBUG_LEVEL': 0, # 0 = lowest level, least verbose, 255 = max level, most verbose ## DO NOT SET TO True IN THIS FILE ## Doing so will cause all courses to be released on production @@ -53,20 +53,20 @@ MITX_FEATURES = { # When True, will only publicly list courses by the subdomain. Expects you # to define COURSE_LISTINGS, a dictionary mapping subdomains to lists of # course_ids (see dev_int.py for an example) - 'SUBDOMAIN_COURSE_LISTINGS' : False, + 'SUBDOMAIN_COURSE_LISTINGS': False, # When True, will override certain branding with university specific values # Expects a SUBDOMAIN_BRANDING dictionary that maps the subdomain to the # university to use for branding purposes 'SUBDOMAIN_BRANDING': False, - 'FORCE_UNIVERSITY_DOMAIN': False, # set this to the university domain to use, as an override to HTTP_HOST + 'FORCE_UNIVERSITY_DOMAIN': False, # set this to the university domain to use, as an override to HTTP_HOST # set to None to do no university selection - 'ENABLE_TEXTBOOK' : True, + 'ENABLE_TEXTBOOK': True, 'ENABLE_DISCUSSION_SERVICE': True, - 'ENABLE_PSYCHOMETRICS': False, # real-time psychometrics (eg item response theory analysis in instructor dashboard) + 'ENABLE_PSYCHOMETRICS': False, # real-time psychometrics (eg item response theory analysis in instructor dashboard) 'ENABLE_SQL_TRACKING_LOGS': False, 'ENABLE_LMS_MIGRATION': False, @@ -74,12 +74,12 @@ MITX_FEATURES = { 'DISABLE_LOGIN_BUTTON': False, # used in systems where login is automatic, eg MIT SSL - 'STUB_VIDEO_FOR_TESTING': False, # do not display video when running automated acceptance tests + 'STUB_VIDEO_FOR_TESTING': False, # do not display video when running automated acceptance tests # extrernal access methods 'ACCESS_REQUIRE_STAFF_FOR_COURSE': False, 'AUTH_USE_OPENID': False, - 'AUTH_USE_MIT_CERTIFICATES' : False, + 'AUTH_USE_MIT_CERTIFICATES': False, 'AUTH_USE_OPENID_PROVIDER': False, } @@ -90,7 +90,7 @@ DEFAULT_GROUPS = [] GENERATE_PROFILE_SCORES = False # Used with XQueue -XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds +XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds ############################# SET PATH INFORMATION ############################# @@ -151,8 +151,8 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.static', 'django.contrib.messages.context_processors.messages', #'django.core.context_processors.i18n', - 'django.contrib.auth.context_processors.auth', #this is required for admin - 'django.core.context_processors.csrf', #necessary for csrf protection + 'django.contrib.auth.context_processors.auth', # this is required for admin + 'django.core.context_processors.csrf', # necessary for csrf protection # Added for django-wiki 'django.core.context_processors.media', @@ -162,7 +162,7 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'course_wiki.course_nav.context_processor', ) -STUDENT_FILEUPLOAD_MAX_SIZE = 4*1000*1000 # 4 MB +STUDENT_FILEUPLOAD_MAX_SIZE = 4 * 1000 * 1000 # 4 MB MAX_FILEUPLOADS_PER_INPUT = 20 # FIXME: @@ -172,7 +172,7 @@ LIB_URL = '/static/js/' # Dev machines shouldn't need the book # BOOK_URL = '/static/book/' -BOOK_URL = 'https://mitxstatic.s3.amazonaws.com/book_images/' # For AWS deploys +BOOK_URL = 'https://mitxstatic.s3.amazonaws.com/book_images/' # For AWS deploys # RSS_URL = r'lms/templates/feed.rss' # PRESS_URL = r'' RSS_TIMEOUT = 600 @@ -268,8 +268,8 @@ STATICFILES_DIRS = [ ] # Locale/Internationalization -TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -LANGUAGE_CODE = 'en' # http://www.i18nguy.com/unicode/language-identifiers.html +TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +LANGUAGE_CODE = 'en' # http://www.i18nguy.com/unicode/language-identifiers.html USE_I18N = True USE_L10N = True @@ -290,7 +290,7 @@ ALLOWED_GITRELOAD_IPS = ['207.97.227.253', '50.57.128.197', '108.171.174.178'] # setting is, I'm just bumping the expiration time to something absurd (100 # years). This is only used if DEFAULT_FILE_STORAGE is overriden to use S3 # in the global settings.py -AWS_QUERYSTRING_EXPIRE = 10 * 365 * 24 * 60 * 60 # 10 years +AWS_QUERYSTRING_EXPIRE = 10 * 365 * 24 * 60 * 60 # 10 years ################################# SIMPLEWIKI ################################### SIMPLE_WIKI_REQUIRE_LOGIN_EDIT = True @@ -299,8 +299,8 @@ SIMPLE_WIKI_REQUIRE_LOGIN_VIEW = False ################################# WIKI ################################### WIKI_ACCOUNT_HANDLING = False WIKI_EDITOR = 'course_wiki.editors.CodeMirror' -WIKI_SHOW_MAX_CHILDREN = 0 # We don't use the little menu that shows children of an article in the breadcrumb -WIKI_ANONYMOUS = False # Don't allow anonymous access until the styling is figured out +WIKI_SHOW_MAX_CHILDREN = 0 # We don't use the little menu that shows children of an article in the breadcrumb +WIKI_ANONYMOUS = False # Don't allow anonymous access until the styling is figured out WIKI_CAN_CHANGE_PERMISSIONS = lambda article, user: user.is_staff or user.is_superuser WIKI_CAN_ASSIGN = lambda article, user: user.is_staff or user.is_superuser @@ -419,7 +419,7 @@ main_vendor_js = [ discussion_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/discussion/**/*.coffee')) staff_grading_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/staff_grading/**/*.coffee')) -open_ended_js = sorted(rooted_glob(PROJECT_ROOT / 'static','coffee/src/open_ended/**/*.coffee')) +open_ended_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/open_ended/**/*.coffee')) PIPELINE_CSS = { 'application': { @@ -476,11 +476,11 @@ PIPELINE_JS = { 'source_filenames': discussion_js, 'output_filename': 'js/discussion.js' }, - 'staff_grading' : { + 'staff_grading': { 'source_filenames': staff_grading_js, 'output_filename': 'js/staff_grading.js' }, - 'open_ended' : { + 'open_ended': { 'source_filenames': open_ended_js, 'output_filename': 'js/open_ended.js' } @@ -563,9 +563,9 @@ INSTALLED_APPS = ( 'course_groups', #For the wiki - 'wiki', # The new django-wiki from benjaoming + 'wiki', # The new django-wiki from benjaoming 'django_notify', - 'course_wiki', # Our customizations + 'course_wiki', # Our customizations 'mptt', 'sekizai', #'wiki.plugins.attachments', diff --git a/lms/envs/content.py b/lms/envs/content.py index 2584dca969..f699153895 100644 --- a/lms/envs/content.py +++ b/lms/envs/content.py @@ -10,7 +10,7 @@ TEMPLATE_DEBUG = True EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' ################################ DEBUG TOOLBAR ################################# -INSTALLED_APPS += ('debug_toolbar',) +INSTALLED_APPS += ('debug_toolbar',) MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',) DEBUG_TOOLBAR_PANELS = ( @@ -24,8 +24,8 @@ DEBUG_TOOLBAR_PANELS = ( 'debug_toolbar.panels.logger.LoggingPanel', # Enabling the profiler has a weird bug as of django-debug-toolbar==0.9.4 and -# Django=1.3.1/1.4 where requests to views get duplicated (your method gets -# hit twice). So you can uncomment when you need to diagnose performance +# Django=1.3.1/1.4 where requests to views get duplicated (your method gets +# hit twice). So you can uncomment when you need to diagnose performance # problems, but you shouldn't leave it on. # 'debug_toolbar.panels.profiling.ProfilingDebugPanel', ) diff --git a/lms/envs/dev.py b/lms/envs/dev.py index 338a31f641..47bcee1b7e 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -165,7 +165,7 @@ INSTALLED_APPS += ('django_openid_auth',) OPENID_CREATE_USERS = False OPENID_UPDATE_DETAILS_FROM_SREG = True -OPENID_SSO_SERVER_URL = 'https://www.google.com/accounts/o8/id' # TODO: accept more endpoints +OPENID_SSO_SERVER_URL = 'https://www.google.com/accounts/o8/id' # TODO: accept more endpoints OPENID_USE_AS_ADMIN_LOGIN = False OPENID_PROVIDER_TRUSTED_ROOTS = ['*'] diff --git a/lms/envs/dev_edx4edx.py b/lms/envs/dev_edx4edx.py index 0212d8b550..c138ed81ae 100644 --- a/lms/envs/dev_edx4edx.py +++ b/lms/envs/dev_edx4edx.py @@ -36,7 +36,7 @@ DEBUG = True ENABLE_MULTICOURSE = True # set to False to disable multicourse display (see lib.util.views.mitxhome) QUICKEDIT = True -MAKO_TEMPLATES['course'] = [DATA_DIR, EDX4EDX_ROOT ] +MAKO_TEMPLATES['course'] = [DATA_DIR, EDX4EDX_ROOT] #MITX_FEATURES['USE_DJANGO_PIPELINE'] = False MITX_FEATURES['DISPLAY_HISTOGRAMS_TO_STAFF'] = False @@ -49,12 +49,12 @@ COURSE_TITLE = "edx4edx: edX Author Course" SITE_NAME = "ichuang.mitx.mit.edu" COURSE_SETTINGS = {'edx4edx': {'number' : 'edX.01', - 'title' : 'edx4edx: edX Author Course', + 'title': 'edx4edx: edX Author Course', 'xmlpath': '/edx4edx/', 'github_url': 'https://github.com/MITx/edx4edx', - 'active' : True, - 'default_chapter' : 'Introduction', - 'default_section' : 'edx4edx_Course', + 'active': True, + 'default_chapter': 'Introduction', + 'default_section': 'edx4edx_Course', }, } diff --git a/lms/envs/dev_ike.py b/lms/envs/dev_ike.py index a17622a81a..639d186989 100644 --- a/lms/envs/dev_ike.py +++ b/lms/envs/dev_ike.py @@ -24,25 +24,25 @@ MITX_FEATURES['DISABLE_START_DATES'] = True myhost = socket.gethostname() if ('edxvm' in myhost) or ('ocw' in myhost): - MITX_FEATURES['DISABLE_LOGIN_BUTTON'] = True # auto-login with MIT certificate - MITX_FEATURES['USE_XQA_SERVER'] = 'https://qisx.mit.edu/xqa' # needs to be ssl or browser blocks it - MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss + MITX_FEATURES['DISABLE_LOGIN_BUTTON'] = True # auto-login with MIT certificate + MITX_FEATURES['USE_XQA_SERVER'] = 'https://qisx.mit.edu/xqa' # needs to be ssl or browser blocks it + MITX_FEATURES['USE_DJANGO_PIPELINE'] = False # don't recompile scss if ('ocw' in myhost): MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = False if ('domU' in myhost): EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' - MITX_FEATURES['REROUTE_ACTIVATION_EMAIL'] = 'ichuang@mitx.mit.edu' # nonempty string = address for all activation emails - MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss + MITX_FEATURES['REROUTE_ACTIVATION_EMAIL'] = 'ichuang@mitx.mit.edu' # nonempty string = address for all activation emails + MITX_FEATURES['USE_DJANGO_PIPELINE'] = False # don't recompile scss -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # django 1.4 for nginx ssl proxy +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # django 1.4 for nginx ssl proxy #----------------------------------------------------------------------------- # disable django debug toolbars -INSTALLED_APPS = tuple([ app for app in INSTALLED_APPS if not app.startswith('debug_toolbar') ]) -MIDDLEWARE_CLASSES = tuple([ mcl for mcl in MIDDLEWARE_CLASSES if not mcl.startswith('debug_toolbar') ]) +INSTALLED_APPS = tuple([app for app in INSTALLED_APPS if not app.startswith('debug_toolbar')]) +MIDDLEWARE_CLASSES = tuple([mcl for mcl in MIDDLEWARE_CLASSES if not mcl.startswith('debug_toolbar')]) #TEMPLATE_LOADERS = tuple([ app for app in TEMPLATE_LOADERS if not app.startswith('mitxmako') ]) TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', diff --git a/lms/envs/dev_int.py b/lms/envs/dev_int.py index 12123e12d4..21c52c8abc 100644 --- a/lms/envs/dev_int.py +++ b/lms/envs/dev_int.py @@ -14,7 +14,7 @@ from .dev import * MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = True COURSE_LISTINGS = { - 'default' : ['BerkeleyX/CS169.1x/2012_Fall', + 'default': ['BerkeleyX/CS169.1x/2012_Fall', 'BerkeleyX/CS188.1x/2012_Fall', 'HarvardX/CS50x/2012', 'HarvardX/PH207x/2012_Fall', @@ -25,8 +25,8 @@ COURSE_LISTINGS = { 'berkeley': ['BerkeleyX/CS169.1x/2012_Fall', 'BerkeleyX/CS188.1x/2012_Fall'], - 'harvard' : ['HarvardX/CS50x/2012'], + 'harvard': ['HarvardX/CS50x/2012'], - 'mit' : ['MITx/3.091x/2012_Fall', + 'mit': ['MITx/3.091x/2012_Fall', 'MITx/6.00x/2012_Fall'] } diff --git a/lms/envs/devgroups/courses.py b/lms/envs/devgroups/courses.py index e9ed28a09d..c44717c451 100644 --- a/lms/envs/devgroups/courses.py +++ b/lms/envs/devgroups/courses.py @@ -1,13 +1,13 @@ from ..dev import * CLASSES_TO_DBS = { - 'BerkeleyX/CS169.1x/2012_Fall' : "cs169.db", - 'BerkeleyX/CS188.1x/2012_Fall' : "cs188_1.db", - 'HarvardX/CS50x/2012' : "cs50.db", - 'HarvardX/PH207x/2012_Fall' : "ph207.db", - 'MITx/3.091x/2012_Fall' : "3091.db", - 'MITx/6.002x/2012_Fall' : "6002.db", - 'MITx/6.00x/2012_Fall' : "600.db", + 'BerkeleyX/CS169.1x/2012_Fall': "cs169.db", + 'BerkeleyX/CS188.1x/2012_Fall': "cs188_1.db", + 'HarvardX/CS50x/2012': "cs50.db", + 'HarvardX/PH207x/2012_Fall': "ph207.db", + 'MITx/3.091x/2012_Fall': "3091.db", + 'MITx/6.002x/2012_Fall': "6002.db", + 'MITx/6.00x/2012_Fall': "600.db", } @@ -20,8 +20,8 @@ CACHES = { 'general': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', - 'KEY_PREFIX' : 'general', - 'VERSION' : 5, + 'KEY_PREFIX': 'general', + 'VERSION': 5, 'KEY_FUNCTION': 'util.memcache.safe_key', } } @@ -32,11 +32,12 @@ SESSION_ENGINE = 'django.contrib.sessions.backends.cache' def path_for_db(db_name): return ENV_ROOT / "db" / db_name + def course_db_for(course_id): db_name = CLASSES_TO_DBS[course_id] return { - 'default' : { - 'ENGINE' : 'django.db.backends.sqlite3', - 'NAME' : path_for_db(db_name) + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': path_for_db(db_name) } } diff --git a/lms/envs/devgroups/portal.py b/lms/envs/devgroups/portal.py index b674218571..35808d56fa 100644 --- a/lms/envs/devgroups/portal.py +++ b/lms/envs/devgroups/portal.py @@ -10,4 +10,3 @@ for class_id, db_name in CLASSES_TO_DBS.items(): 'ENGINE': 'django.db.backends.sqlite3', 'NAME': path_for_db(db_name) } - diff --git a/lms/envs/devplus.py b/lms/envs/devplus.py index 5c79304c0a..ea6590291c 100644 --- a/lms/envs/devplus.py +++ b/lms/envs/devplus.py @@ -37,8 +37,8 @@ CACHES = { 'general': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', - 'KEY_PREFIX' : 'general', - 'VERSION' : 5, + 'KEY_PREFIX': 'general', + 'VERSION': 5, 'KEY_FUNCTION': 'util.memcache.safe_key', } } @@ -47,7 +47,7 @@ SESSION_ENGINE = 'django.contrib.sessions.backends.cache' ################################ DEBUG TOOLBAR ################################# -INSTALLED_APPS += ('debug_toolbar',) +INSTALLED_APPS += ('debug_toolbar',) MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',) INTERNAL_IPS = ('127.0.0.1',) @@ -62,8 +62,8 @@ DEBUG_TOOLBAR_PANELS = ( 'debug_toolbar.panels.logger.LoggingPanel', # Enabling the profiler has a weird bug as of django-debug-toolbar==0.9.4 and -# Django=1.3.1/1.4 where requests to views get duplicated (your method gets -# hit twice). So you can uncomment when you need to diagnose performance +# Django=1.3.1/1.4 where requests to views get duplicated (your method gets +# hit twice). So you can uncomment when you need to diagnose performance # problems, but you shouldn't leave it on. 'debug_toolbar.panels.profiling.ProfilingDebugPanel', ) diff --git a/lms/envs/edx4edx_aws.py b/lms/envs/edx4edx_aws.py index adc2c6e1ce..de377c0b57 100644 --- a/lms/envs/edx4edx_aws.py +++ b/lms/envs/edx4edx_aws.py @@ -5,21 +5,21 @@ COURSE_NUMBER = "edX.01" COURSE_TITLE = "edx4edx: edX Author Course" EDX4EDX_ROOT = ENV_ROOT / "data/edx4edx" -### Dark code. Should be enabled in local settings for devel. +### Dark code. Should be enabled in local settings for devel. QUICKEDIT = True -ENABLE_MULTICOURSE = True # set to False to disable multicourse display (see lib.util.views.mitxhome) +ENABLE_MULTICOURSE = True # set to False to disable multicourse display (see lib.util.views.mitxhome) ### PIPELINE_CSS_COMPRESSOR = None PIPELINE_JS_COMPRESSOR = None COURSE_DEFAULT = 'edx4edx' COURSE_SETTINGS = {'edx4edx': {'number' : 'edX.01', - 'title' : 'edx4edx: edX Author Course', + 'title': 'edx4edx: edX Author Course', 'xmlpath': '/edx4edx/', 'github_url': 'https://github.com/MITx/edx4edx', - 'active' : True, - 'default_chapter' : 'Introduction', - 'default_section' : 'edx4edx_Course', + 'active': True, + 'default_chapter': 'Introduction', + 'default_section': 'edx4edx_Course', }, } @@ -34,4 +34,4 @@ STATICFILES_DIRS = [ # ("book", ENV_ROOT / "book_images") ] -MAKO_TEMPLATES['course'] = [DATA_DIR, EDX4EDX_ROOT ] +MAKO_TEMPLATES['course'] = [DATA_DIR, EDX4EDX_ROOT] diff --git a/lms/envs/static.py b/lms/envs/static.py index f233571a9e..23e735c747 100644 --- a/lms/envs/static.py +++ b/lms/envs/static.py @@ -12,7 +12,7 @@ from logsettings import get_logger_config STATIC_GRAB = True -LOGGING = get_logger_config(ENV_ROOT / "log", +LOGGING = get_logger_config(ENV_ROOT / "log", logging_env="dev", tracking_filename="tracking.log", debug=False) @@ -25,7 +25,7 @@ DATABASES = { } CACHES = { - # This is the cache used for most things. + # This is the cache used for most things. # In staging/prod envs, the sessions also live here. 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', diff --git a/lms/envs/test.py b/lms/envs/test.py index 8b546549eb..6cad6416d0 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -23,7 +23,7 @@ MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = False WIKI_ENABLED = True # Makes the tests run much faster... -SOUTH_TESTS_MIGRATE = False # To disable migrations and use syncdb instead +SOUTH_TESTS_MIGRATE = False # To disable migrations and use syncdb instead # Nose Test Runner INSTALLED_APPS += ('django_nose',) @@ -57,7 +57,7 @@ XQUEUE_INTERFACE = { }, "basic_auth": ('anant', 'agarwal'), } -XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds +XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds # Don't rely on a real staff grading backend diff --git a/lms/envs/test_ike.py b/lms/envs/test_ike.py index b162d9e2bb..907b7eeadf 100644 --- a/lms/envs/test_ike.py +++ b/lms/envs/test_ike.py @@ -34,11 +34,11 @@ DATA_DIR = COURSES_ROOT MAKO_TEMPLATES['course'] = [DATA_DIR] MAKO_TEMPLATES['sections'] = [DATA_DIR / 'sections'] MAKO_TEMPLATES['custom_tags'] = [DATA_DIR / 'custom_tags'] -MAKO_TEMPLATES['main'] = [PROJECT_ROOT / 'templates', +MAKO_TEMPLATES['main'] = [PROJECT_ROOT / 'templates', DATA_DIR / 'info', DATA_DIR / 'problems'] -LOGGING = get_logger_config(TEST_ROOT / "log", +LOGGING = get_logger_config(TEST_ROOT / "log", logging_env="dev", tracking_filename="tracking.log", debug=True) @@ -51,7 +51,7 @@ DATABASES = { } CACHES = { - # This is the cache used for most things. + # This is the cache used for most things. # In staging/prod envs, the sessions also live here. 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', diff --git a/lms/lib/comment_client/comment.py b/lms/lib/comment_client/comment.py index 6d0bafee02..0d81761512 100644 --- a/lms/lib/comment_client/comment.py +++ b/lms/lib/comment_client/comment.py @@ -4,6 +4,7 @@ from thread import Thread import models import settings + class Comment(models.Model): accessible_fields = [ @@ -41,8 +42,10 @@ class Comment(models.Model): else: return super(Comment, cls).url(action, params) + def _url_for_thread_comments(thread_id): return "{prefix}/threads/{thread_id}/comments".format(prefix=settings.PREFIX, thread_id=thread_id) + def _url_for_comment(comment_id): return "{prefix}/comments/{comment_id}".format(prefix=settings.PREFIX, comment_id=comment_id) diff --git a/lms/lib/comment_client/comment_client.py b/lms/lib/comment_client/comment_client.py index fe485433d6..d7c8f05485 100644 --- a/lms/lib/comment_client/comment_client.py +++ b/lms/lib/comment_client/comment_client.py @@ -7,32 +7,40 @@ from utils import * import settings + def search_similar_threads(course_id, recursive=False, query_params={}, *args, **kwargs): default_params = {'course_id': course_id, 'recursive': recursive} attributes = dict(default_params.items() + query_params.items()) return perform_request('get', _url_for_search_similar_threads(), attributes, *args, **kwargs) + def search_recent_active_threads(course_id, recursive=False, query_params={}, *args, **kwargs): default_params = {'course_id': course_id, 'recursive': recursive} attributes = dict(default_params.items() + query_params.items()) return perform_request('get', _url_for_search_recent_active_threads(), attributes, *args, **kwargs) + def search_trending_tags(course_id, query_params={}, *args, **kwargs): default_params = {'course_id': course_id} attributes = dict(default_params.items() + query_params.items()) return perform_request('get', _url_for_search_trending_tags(), attributes, *args, **kwargs) + def tags_autocomplete(value, *args, **kwargs): return perform_request('get', _url_for_threads_tags_autocomplete(), {'value': value}, *args, **kwargs) + def _url_for_search_similar_threads(): return "{prefix}/search/threads/more_like_this".format(prefix=settings.PREFIX) + def _url_for_search_recent_active_threads(): return "{prefix}/search/threads/recent_active".format(prefix=settings.PREFIX) + def _url_for_search_trending_tags(): return "{prefix}/search/tags/trending".format(prefix=settings.PREFIX) + def _url_for_threads_tags_autocomplete(): return "{prefix}/threads/tags/autocomplete".format(prefix=settings.PREFIX) diff --git a/lms/lib/comment_client/commentable.py b/lms/lib/comment_client/commentable.py index 8f91bfc93d..85c357ef81 100644 --- a/lms/lib/comment_client/commentable.py +++ b/lms/lib/comment_client/commentable.py @@ -3,6 +3,7 @@ from utils import * import models import settings + class Commentable(models.Model): base_url = "{prefix}/commentables".format(prefix=settings.PREFIX) diff --git a/lms/lib/comment_client/legacy.py b/lms/lib/comment_client/legacy.py index fc87bcaf4f..fbf66a09fd 100644 --- a/lms/lib/comment_client/legacy.py +++ b/lms/lib/comment_client/legacy.py @@ -1,6 +1,7 @@ def delete_threads(commentable_id, *args, **kwargs): return _perform_request('delete', _url_for_commentable_threads(commentable_id), *args, **kwargs) + def get_threads(commentable_id, recursive=False, query_params={}, *args, **kwargs): default_params = {'page': 1, 'per_page': 20, 'recursive': recursive} attributes = dict(default_params.items() + query_params.items()) @@ -8,6 +9,7 @@ def get_threads(commentable_id, recursive=False, query_params={}, *args, **kwarg attributes, *args, **kwargs) return response.get('collection', []), response.get('page', 1), response.get('num_pages', 1) + def search_threads(course_id, recursive=False, query_params={}, *args, **kwargs): default_params = {'page': 1, 'per_page': 20, 'course_id': course_id, 'recursive': recursive} attributes = dict(default_params.items() + query_params.items()) @@ -15,106 +17,137 @@ def search_threads(course_id, recursive=False, query_params={}, *args, **kwargs) attributes, *args, **kwargs) return response.get('collection', []), response.get('page', 1), response.get('num_pages', 1) + def search_similar_threads(course_id, recursive=False, query_params={}, *args, **kwargs): default_params = {'course_id': course_id, 'recursive': recursive} attributes = dict(default_params.items() + query_params.items()) return _perform_request('get', _url_for_search_similar_threads(), attributes, *args, **kwargs) + def search_recent_active_threads(course_id, recursive=False, query_params={}, *args, **kwargs): default_params = {'course_id': course_id, 'recursive': recursive} attributes = dict(default_params.items() + query_params.items()) return _perform_request('get', _url_for_search_recent_active_threads(), attributes, *args, **kwargs) + def search_trending_tags(course_id, query_params={}, *args, **kwargs): default_params = {'course_id': course_id} attributes = dict(default_params.items() + query_params.items()) return _perform_request('get', _url_for_search_trending_tags(), attributes, *args, **kwargs) + def create_user(attributes, *args, **kwargs): return _perform_request('post', _url_for_users(), attributes, *args, **kwargs) + def update_user(user_id, attributes, *args, **kwargs): return _perform_request('put', _url_for_user(user_id), attributes, *args, **kwargs) + def get_threads_tags(*args, **kwargs): return _perform_request('get', _url_for_threads_tags(), {}, *args, **kwargs) + def tags_autocomplete(value, *args, **kwargs): return _perform_request('get', _url_for_threads_tags_autocomplete(), {'value': value}, *args, **kwargs) + def create_thread(commentable_id, attributes, *args, **kwargs): return _perform_request('post', _url_for_threads(commentable_id), attributes, *args, **kwargs) - + + def get_thread(thread_id, recursive=False, *args, **kwargs): return _perform_request('get', _url_for_thread(thread_id), {'recursive': recursive}, *args, **kwargs) + def update_thread(thread_id, attributes, *args, **kwargs): return _perform_request('put', _url_for_thread(thread_id), attributes, *args, **kwargs) + def create_comment(thread_id, attributes, *args, **kwargs): return _perform_request('post', _url_for_thread_comments(thread_id), attributes, *args, **kwargs) + def delete_thread(thread_id, *args, **kwargs): return _perform_request('delete', _url_for_thread(thread_id), *args, **kwargs) + def get_comment(comment_id, recursive=False, *args, **kwargs): return _perform_request('get', _url_for_comment(comment_id), {'recursive': recursive}, *args, **kwargs) + def update_comment(comment_id, attributes, *args, **kwargs): return _perform_request('put', _url_for_comment(comment_id), attributes, *args, **kwargs) + def create_sub_comment(comment_id, attributes, *args, **kwargs): return _perform_request('post', _url_for_comment(comment_id), attributes, *args, **kwargs) + def delete_comment(comment_id, *args, **kwargs): return _perform_request('delete', _url_for_comment(comment_id), *args, **kwargs) + def vote_for_comment(comment_id, user_id, value, *args, **kwargs): return _perform_request('put', _url_for_vote_comment(comment_id), {'user_id': user_id, 'value': value}, *args, **kwargs) + def undo_vote_for_comment(comment_id, user_id, *args, **kwargs): return _perform_request('delete', _url_for_vote_comment(comment_id), {'user_id': user_id}, *args, **kwargs) + def vote_for_thread(thread_id, user_id, value, *args, **kwargs): return _perform_request('put', _url_for_vote_thread(thread_id), {'user_id': user_id, 'value': value}, *args, **kwargs) + def undo_vote_for_thread(thread_id, user_id, *args, **kwargs): return _perform_request('delete', _url_for_vote_thread(thread_id), {'user_id': user_id}, *args, **kwargs) + def get_notifications(user_id, *args, **kwargs): return _perform_request('get', _url_for_notifications(user_id), *args, **kwargs) + def get_user_info(user_id, complete=True, *args, **kwargs): return _perform_request('get', _url_for_user(user_id), {'complete': complete}, *args, **kwargs) + def subscribe(user_id, subscription_detail, *args, **kwargs): return _perform_request('post', _url_for_subscription(user_id), subscription_detail, *args, **kwargs) + def subscribe_user(user_id, followed_user_id, *args, **kwargs): return subscribe(user_id, {'source_type': 'user', 'source_id': followed_user_id}) follow = subscribe_user + def subscribe_thread(user_id, thread_id, *args, **kwargs): return subscribe(user_id, {'source_type': 'thread', 'source_id': thread_id}) + def subscribe_commentable(user_id, commentable_id, *args, **kwargs): return subscribe(user_id, {'source_type': 'other', 'source_id': commentable_id}) + def unsubscribe(user_id, subscription_detail, *args, **kwargs): return _perform_request('delete', _url_for_subscription(user_id), subscription_detail, *args, **kwargs) + def unsubscribe_user(user_id, followed_user_id, *args, **kwargs): return unsubscribe(user_id, {'source_type': 'user', 'source_id': followed_user_id}) unfollow = unsubscribe_user + def unsubscribe_thread(user_id, thread_id, *args, **kwargs): return unsubscribe(user_id, {'source_type': 'thread', 'source_id': thread_id}) + def unsubscribe_commentable(user_id, commentable_id, *args, **kwargs): return unsubscribe(user_id, {'source_type': 'other', 'source_id': commentable_id}) + def _perform_request(method, url, data_or_params=None, *args, **kwargs): if method in ['post', 'put', 'patch']: response = requests.request(method, url, data=data_or_params) @@ -130,51 +163,66 @@ def _perform_request(method, url, data_or_params=None, *args, **kwargs): else: return json.loads(response.text) + def _url_for_threads(commentable_id): return "{prefix}/{commentable_id}/threads".format(prefix=PREFIX, commentable_id=commentable_id) + def _url_for_thread(thread_id): return "{prefix}/threads/{thread_id}".format(prefix=PREFIX, thread_id=thread_id) + def _url_for_thread_comments(thread_id): return "{prefix}/threads/{thread_id}/comments".format(prefix=PREFIX, thread_id=thread_id) + def _url_for_comment(comment_id): return "{prefix}/comments/{comment_id}".format(prefix=PREFIX, comment_id=comment_id) + def _url_for_vote_comment(comment_id): return "{prefix}/comments/{comment_id}/votes".format(prefix=PREFIX, comment_id=comment_id) + def _url_for_vote_thread(thread_id): return "{prefix}/threads/{thread_id}/votes".format(prefix=PREFIX, thread_id=thread_id) + def _url_for_notifications(user_id): return "{prefix}/users/{user_id}/notifications".format(prefix=PREFIX, user_id=user_id) + def _url_for_subscription(user_id): return "{prefix}/users/{user_id}/subscriptions".format(prefix=PREFIX, user_id=user_id) + def _url_for_user(user_id): return "{prefix}/users/{user_id}".format(prefix=PREFIX, user_id=user_id) + def _url_for_search_threads(): return "{prefix}/search/threads".format(prefix=PREFIX) + def _url_for_search_similar_threads(): return "{prefix}/search/threads/more_like_this".format(prefix=PREFIX) + def _url_for_search_recent_active_threads(): return "{prefix}/search/threads/recent_active".format(prefix=PREFIX) + def _url_for_search_trending_tags(): return "{prefix}/search/tags/trending".format(prefix=PREFIX) + def _url_for_threads_tags(): return "{prefix}/threads/tags".format(prefix=PREFIX) + def _url_for_threads_tags_autocomplete(): return "{prefix}/threads/tags/autocomplete".format(prefix=PREFIX) + def _url_for_users(): return "{prefix}/users".format(prefix=PREFIX) - diff --git a/lms/lib/comment_client/models.py b/lms/lib/comment_client/models.py index 3ce3858d2d..8676d3af33 100644 --- a/lms/lib/comment_client/models.py +++ b/lms/lib/comment_client/models.py @@ -1,5 +1,6 @@ from utils import * + class Model(object): accessible_fields = ['id'] @@ -26,7 +27,7 @@ class Model(object): raise AttributeError("Field {0} does not exist".format(name)) self.retrieve() return self.__getattr__(name) - + def __setattr__(self, name, value): if name == 'attributes' or name not in self.accessible_fields: super(Model, self).__setattr__(name, value) @@ -80,7 +81,7 @@ class Model(object): def initializable_attributes(self): return extract(self.attributes, self.initializable_fields) - + @classmethod def before_save(cls, instance): pass @@ -91,10 +92,10 @@ class Model(object): def save(self): self.__class__.before_save(self) - if self.id: # if we have id already, treat this as an update + if self.id: # if we have id already, treat this as an update url = self.url(action='put', params=self.attributes) response = perform_request('put', url, self.updatable_attributes()) - else: # otherwise, treat this as an insert + else: # otherwise, treat this as an insert url = self.url(action='post', params=self.attributes) response = perform_request('post', url, self.initializable_attributes()) self.retrieved = True @@ -126,5 +127,5 @@ class Model(object): return cls.url_with_id(params) except KeyError: raise CommentClientError("Cannot perform action {0} without id".format(action)) - else: # action must be in DEFAULT_ACTIONS_WITHOUT_ID now + else: # action must be in DEFAULT_ACTIONS_WITHOUT_ID now return cls.url_without_id() diff --git a/lms/lib/comment_client/thread.py b/lms/lib/comment_client/thread.py index 6fd31b0823..912ae1af18 100644 --- a/lms/lib/comment_client/thread.py +++ b/lms/lib/comment_client/thread.py @@ -3,6 +3,7 @@ from utils import * import models import settings + class Thread(models.Model): accessible_fields = [ @@ -66,7 +67,7 @@ class Thread(models.Model): def _retrieve(self, *args, **kwargs): url = self.url(action='get', params=self.attributes) - request_params = { + request_params = { 'recursive': kwargs.get('recursive'), 'user_id': kwargs.get('user_id'), 'mark_as_read': kwargs.get('mark_as_read', True), diff --git a/lms/lib/comment_client/user.py b/lms/lib/comment_client/user.py index 546b27556c..c3ba84175e 100644 --- a/lms/lib/comment_client/user.py +++ b/lms/lib/comment_client/user.py @@ -3,6 +3,7 @@ from utils import * import models import settings + class User(models.Model): accessible_fields = ['username', 'email', 'follower_ids', 'upvoted_ids', 'downvoted_ids', @@ -82,17 +83,22 @@ class User(models.Model): response = perform_request('get', url, retrieve_params) self.update_attributes(**response) + def _url_for_vote_comment(comment_id): return "{prefix}/comments/{comment_id}/votes".format(prefix=settings.PREFIX, comment_id=comment_id) + def _url_for_vote_thread(thread_id): return "{prefix}/threads/{thread_id}/votes".format(prefix=settings.PREFIX, thread_id=thread_id) + def _url_for_subscription(user_id): return "{prefix}/users/{user_id}/subscriptions".format(prefix=settings.PREFIX, user_id=user_id) + def _url_for_user_active_threads(user_id): return "{prefix}/users/{user_id}/active_threads".format(prefix=settings.PREFIX, user_id=user_id) + def _url_for_user_subscribed_threads(user_id): return "{prefix}/users/{user_id}/subscribed_threads".format(prefix=settings.PREFIX, user_id=user_id) diff --git a/lms/lib/comment_client/utils.py b/lms/lib/comment_client/utils.py index f50797d5e0..e053fea6c0 100644 --- a/lms/lib/comment_client/utils.py +++ b/lms/lib/comment_client/utils.py @@ -5,23 +5,28 @@ import settings log = logging.getLogger('mitx.' + __name__) + def strip_none(dic): return dict([(k, v) for k, v in dic.iteritems() if v is not None]) + def strip_blank(dic): def _is_blank(v): return isinstance(v, str) and len(v.strip()) == 0 return dict([(k, v) for k, v in dic.iteritems() if not _is_blank(v)]) + def extract(dic, keys): if isinstance(keys, str): return strip_none({keys: dic.get(keys)}) else: return strip_none({k: dic.get(k) for k in keys}) + def merge_dict(dic1, dic2): return dict(dic1.items() + dic2.items()) + def perform_request(method, url, data_or_params=None, *args, **kwargs): if data_or_params is None: data_or_params = {} @@ -34,7 +39,7 @@ def perform_request(method, url, data_or_params=None, *args, **kwargs): except Exception as err: log.exception("Trying to call {method} on {url} with params {params}".format( method=method, url=url, params=data_or_params)) - # Reraise with a single exception type + # Reraise with a single exception type raise CommentClientError(str(err)) if 200 < response.status_code < 500: @@ -47,6 +52,7 @@ def perform_request(method, url, data_or_params=None, *args, **kwargs): else: return json.loads(response.text) + class CommentClientError(Exception): def __init__(self, msg): self.message = msg @@ -54,5 +60,6 @@ class CommentClientError(Exception): def __str__(self): return repr(self.message) + class CommentClientUnknownError(CommentClientError): pass diff --git a/lms/lib/dogfood/check.py b/lms/lib/dogfood/check.py index 0a1da38529..070d3f9262 100644 --- a/lms/lib/dogfood/check.py +++ b/lms/lib/dogfood/check.py @@ -59,5 +59,3 @@ def check_problem_code(ans, the_lcp, correct_answers, false_answers): 'msg': msg + endmsg, } return ret - - diff --git a/lms/lib/loncapa/loncapa_check.py b/lms/lib/loncapa/loncapa_check.py index 0fd998e00e..2cd591520e 100644 --- a/lms/lib/loncapa/loncapa_check.py +++ b/lms/lib/loncapa/loncapa_check.py @@ -33,5 +33,3 @@ def lc_choose(index, *args): deg2rad = math.pi / 180.0 rad2deg = 180.0 / math.pi - - diff --git a/lms/lib/symmath/symmath_check.py b/lms/lib/symmath/symmath_check.py index 3cc4fd7d3c..a3dec4aae5 100644 --- a/lms/lib/symmath/symmath_check.py +++ b/lms/lib/symmath/symmath_check.py @@ -143,6 +143,7 @@ def check(expect, given, numerical=False, matrix=False, normphase=False, abcsym= #----------------------------------------------------------------------------- # helper function to convert all

    to + def make_error_message(msg): # msg = msg.replace('

    ','

    ').replace('

    ','

    ') msg = '
    %s
    ' % msg @@ -153,6 +154,7 @@ def make_error_message(msg): # # This is one of the main entry points to call. + def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None): ''' Check a symbolic mathematical expression using sympy. @@ -183,12 +185,12 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None # msg += '

    abname=%s' % abname # msg += '

    adict=%s' % (repr(adict).replace('<','<')) - threshold = 1.0e-3 # for numerical comparison (also with matrices) + threshold = 1.0e-3 # for numerical comparison (also with matrices) DEBUG = debug if xml is not None: - DEBUG = xml.get('debug',False) # override debug flag using attribute in symbolicmath xml - if DEBUG in ['0','False']: + DEBUG = xml.get('debug', False) # override debug flag using attribute in symbolicmath xml + if DEBUG in ['0', 'False']: DEBUG = False # options diff --git a/lms/static/admin/js/compress.py b/lms/static/admin/js/compress.py index 8d2caa28ea..a23b431750 100644 --- a/lms/static/admin/js/compress.py +++ b/lms/static/admin/js/compress.py @@ -6,6 +6,7 @@ import sys here = os.path.dirname(__file__) + def main(): usage = "usage: %prog [file1..fileN]" description = """With no file paths given this script will automatically diff --git a/lms/urls.py b/lms/urls.py index e9746c2338..b25c4d259e 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -13,7 +13,7 @@ urlpatterns = ('', # certificate view url(r'^update_certificate$', 'certificates.views.update_certificate'), - url(r'^$', 'branding.views.index', name="root"), # Main marketing page, or redirect to courseware + url(r'^$', 'branding.views.index', name="root"), # Main marketing page, or redirect to courseware url(r'^dashboard$', 'student.views.dashboard', name="dashboard"), url(r'^admin_dashboard$', 'dashboard.views.dashboard'), @@ -35,7 +35,7 @@ urlpatterns = ('', # url(r'^testcenter/logout$', 'student.test_center_views.logout'), url(r'^event$', 'track.views.user_track'), - url(r'^t/(?P

    + ${i} points +
    ${category['description']} + %else: + % endif % if view_only: ## if this is the selected rubric block, show it highlighted - % if option['selected']: -
    - % else:
    - % endif ${option['text']}
    % else: From ba30c2ce58b9478483ced3857b39a4d1d9b88235 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 10:17:16 -0500 Subject: [PATCH 029/444] Calculate the total score from the rubric. --- .../xmodule/xmodule/js/src/combinedopenended/display.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index c4560559c8..576fb7290d 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -16,6 +16,10 @@ class @Rubric @get_total_score: () -> score_lst = @get_score_list() + tot = 0 + for score in score_lst + tot += parseInt(score) + return tot @check_complete: () -> # check to see whether or not any categories have not been scored From 745c05277521ef57593de2071b3afeeb4e786a24 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 10:31:46 -0500 Subject: [PATCH 030/444] Remove duplicate Javascript and remove total grade selection. --- .../peer_grading/peer_grading_problem.coffee | 35 ++----------------- .../src/staff_grading/staff_grading.coffee | 15 -------- 2 files changed, 3 insertions(+), 47 deletions(-) diff --git a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee index 525891bb03..f4b9bdbe78 100644 --- a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee +++ b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee @@ -232,23 +232,11 @@ class PeerGradingProblem fetch_submission_essay: () => @backend.post('get_next_submission', {location: @location}, @render_submission) - # finds the scores for each rubric category - get_score_list: () => - # find the number of categories: - num_categories = $('table.rubric tr').length - - score_lst = [] - # get the score for each one - for i in [0..(num_categories-2)] - score = $("input[name='score-selection-#{i}']:checked").val() - score_lst.push(score) - - return score_lst construct_data: () -> data = - rubric_scores: @get_score_list() - score: @grade + rubric_scores: Rubric.get_score_list() + score: Rubric.get_total_score() location: @location submission_id: @essay_id_input.val() submission_key: @submission_key_input.val() @@ -316,7 +304,7 @@ class PeerGradingProblem # called after a grade is selected on the interface graded_callback: (event) => # check to see whether or not any categories have not been scored - if Rubric.check_complete(): + if Rubric.check_complete() # show button if we have scores for all categories @show_submit_button() @@ -439,25 +427,8 @@ class PeerGradingProblem setup_score_selection: (max_score) => - # first, get rid of all the old inputs, if any. - @score_selection_container.html(""" -

    Overall Score

    -

    Choose an overall score for this submission.

    - """) - - # Now create new labels and inputs for each possible score. - for score in [0..max_score] - id = 'score-' + score - label = """""" - - input = """ - - """ # " fix broken parsing in emacs - @score_selection_container.append(input + label) - # And now hook up an event handler again $("input[name='score-selection']").change @graded_callback - $("input[name='grade-selection']").change @graded_callback diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 2d3cafd3e7..117388bab0 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -212,21 +212,6 @@ class @StaffGrading setup_score_selection: => - # first, get rid of all the old inputs, if any. - @grade_selection_container.html(""" -

    Overall Score

    -

    Choose an overall score for this submission.

    - """) - # Now create new labels and inputs for each possible score. - for score in [0..@max_score] - id = 'score-' + score - label = """""" - input = """ - - """ # " fix broken parsing in emacs - @grade_selection_container.append(input + label) - $('.grade-selection').click => @graded_callback() - @score_selection_container.html(@rubric) $('.score-selection').click => @graded_callback() From 68fc794a9757b66ddd92166a5529d02050115849 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 11:21:56 -0500 Subject: [PATCH 031/444] Add in some better encouragement to write feedback --- lms/templates/instructor/staff_grading.html | 1 + lms/templates/peer_grading/peer_grading_problem.html | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lms/templates/instructor/staff_grading.html b/lms/templates/instructor/staff_grading.html index 56aed5a54a..dcfece34b8 100644 --- a/lms/templates/instructor/staff_grading.html +++ b/lms/templates/instructor/staff_grading.html @@ -75,6 +75,7 @@

    +

    Written Feedback

    diff --git a/lms/templates/peer_grading/peer_grading_problem.html b/lms/templates/peer_grading/peer_grading_problem.html index cb9ed1c0fb..ae630f118e 100644 --- a/lms/templates/peer_grading/peer_grading_problem.html +++ b/lms/templates/peer_grading/peer_grading_problem.html @@ -70,7 +70,9 @@

    - From 1c5c54c862fb106b1efda09a1da35367e7fe35cc Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 11:27:53 -0500 Subject: [PATCH 032/444] Make the rubric for self-assessment selectable and remove the separate grade selection. --- .../js/src/combinedopenended/display.coffee | 6 +++--- .../xmodule/xmodule/self_assessment_module.py | 2 +- .../src/peer_grading/peer_grading_problem.coffee | 1 - lms/templates/self_assessment_rubric.html | 16 ---------------- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 576fb7290d..9add338137 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -208,9 +208,9 @@ class @CombinedOpenEnded save_assessment: (event) => event.preventDefault() - if @child_state == 'assessing' - checked_assessment = @$('input[name="grade-selection"]:checked') - data = {'assessment' : checked_assessment.val()} + if @child_state == 'assessing' && Rubric.check_complete() + checked_assessment = Rubric.get_total_score() + data = {'assessment' : checked_assessment} $.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) => if response.success @child_state = response.state diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index fb1d306708..a288fa55b3 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -122,7 +122,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): if self.state == self.INITIAL: return '' - rubric_renderer = CombinedOpenEndedRubric(system, True) + rubric_renderer = CombinedOpenEndedRubric(system, False) rubric_html = rubric_renderer.render_rubric(self.rubric) # we'll render it diff --git a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee index f4b9bdbe78..f803c74c7b 100644 --- a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee +++ b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee @@ -426,7 +426,6 @@ class PeerGradingProblem @submit_button.show() setup_score_selection: (max_score) => - # And now hook up an event handler again $("input[name='score-selection']").change @graded_callback diff --git a/lms/templates/self_assessment_rubric.html b/lms/templates/self_assessment_rubric.html index b4fc125232..2986c5041a 100644 --- a/lms/templates/self_assessment_rubric.html +++ b/lms/templates/self_assessment_rubric.html @@ -2,20 +2,4 @@
    ${rubric | n }
    - - % if not read_only: -
    -

    Scoring

    -

    Please select a score below:

    - -
    - %for i in xrange(0,max_score+1): - <% id = "score-{0}".format(i) %> - - - %endfor -
    -
    - % endif - From 71c233af15d4cc0c06af8bfd3d263a8678f9b7d0 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 13:06:12 -0500 Subject: [PATCH 033/444] Fix bug in the callback --- lms/static/coffee/src/peer_grading/peer_grading_problem.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee index f803c74c7b..05d0189ac8 100644 --- a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee +++ b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee @@ -427,7 +427,7 @@ class PeerGradingProblem setup_score_selection: (max_score) => # And now hook up an event handler again - $("input[name='score-selection']").change @graded_callback + $("input[class='score-selection']").change @graded_callback From a85d43a8170d6b00d1a9232ce665fd558d83bb1f Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Feb 2013 14:34:09 -0500 Subject: [PATCH 034/444] Make rubrics spacing smaller and fix a bug in the grading service renderer --- lms/djangoapps/open_ended_grading/grading_service.py | 2 +- lms/static/sass/course/_rubric.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/open_ended_grading/grading_service.py b/lms/djangoapps/open_ended_grading/grading_service.py index f65554a9d6..8e6209bf38 100644 --- a/lms/djangoapps/open_ended_grading/grading_service.py +++ b/lms/djangoapps/open_ended_grading/grading_service.py @@ -115,7 +115,7 @@ class GradingService(object): response_json = json.loads(response) if 'rubric' in response_json: rubric = response_json['rubric'] - rubric_renderer = CombinedOpenEndedRubric(self.system, False) + rubric_renderer = CombinedOpenEndedRubric(self.system, view_only) rubric_html = rubric_renderer.render_rubric(rubric) response_json['rubric'] = rubric_html return response_json diff --git a/lms/static/sass/course/_rubric.scss b/lms/static/sass/course/_rubric.scss index 722a790e6d..5048d70253 100644 --- a/lms/static/sass/course/_rubric.scss +++ b/lms/static/sass/course/_rubric.scss @@ -21,7 +21,7 @@ .rubric-label { position: relative; - padding: 15px 15px 25px; + padding: 0px 15px 15px 15px; width: 130px; min-height: 50px; min-width: 50px; From 9d98b7055de26a4f70f0e987c8f682786564441d Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Fri, 1 Feb 2013 17:52:14 -0500 Subject: [PATCH 035/444] add course navigation back into timed exam. Add initial timer styling. --- lms/djangoapps/courseware/views.py | 12 +++++--- lms/static/sass/course.scss | 2 +- lms/static/sass/course/layout/_timer.scss | 11 +++++++ lms/templates/courseware/testcenter_exam.html | 29 +++++++++++++++++-- lms/urls.py | 6 ++++ 5 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 lms/static/sass/course/layout/_timer.scss diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 1ac7cebd4b..47942f3a63 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -352,7 +352,6 @@ def timed_exam(request, course_id, chapter, section): context = { 'csrf': csrf(request)['csrf_token'], - # 'accordion': render_accordion(request, course, chapter, section), 'COURSE_TITLE': course.title, 'course': course, 'init': '', @@ -361,6 +360,11 @@ def timed_exam(request, course_id, chapter, section): 'xqa_server': settings.MITX_FEATURES.get('USE_XQA_SERVER','http://xqa:server@content-qa.mitx.mit.edu/xqa') } + # in general, we may want to disable accordion display on timed exams. + provide_accordion = True + if provide_accordion: + context['accordion'] = render_accordion(request, course, chapter, section) + chapter_descriptor = course.get_child_by(lambda m: m.url_name == chapter) if chapter_descriptor is not None: instance_module = get_instance_module(course_id, request.user, course_module, student_module_cache) @@ -387,9 +391,9 @@ def timed_exam(request, course_id, chapter, section): # they don't have access to. raise Http404 - # Save where we are in the chapter NOT! -# instance_module = get_instance_module(course_id, request.user, chapter_module, student_module_cache) -# save_child_position(chapter_module, section, instance_module) + # Save where we are in the chapter: + instance_module = get_instance_module(course_id, request.user, chapter_module, student_module_cache) + save_child_position(chapter_module, section, instance_module) context['content'] = section_module.get_html() diff --git a/lms/static/sass/course.scss b/lms/static/sass/course.scss index e900e589b2..7c2522a194 100644 --- a/lms/static/sass/course.scss +++ b/lms/static/sass/course.scss @@ -22,7 +22,7 @@ @import 'course/courseware/sidebar'; @import 'course/courseware/amplifier'; @import 'course/layout/calculator'; - +@import 'course/layout/timer'; // course-specific courseware (all styles in these files should be gated by a // course-specific class). This should be replaced with a better way of diff --git a/lms/static/sass/course/layout/_timer.scss b/lms/static/sass/course/layout/_timer.scss new file mode 100644 index 0000000000..01d62d87c7 --- /dev/null +++ b/lms/static/sass/course/layout/_timer.scss @@ -0,0 +1,11 @@ +div.timer-main { + position: fixed; + z-index: 99; + width: 100%; + + div#timer_wrapper { + position: relative; + float: right; + margin-right: 10px; + } +} diff --git a/lms/templates/courseware/testcenter_exam.html b/lms/templates/courseware/testcenter_exam.html index 638778f7cd..d2f74ab296 100644 --- a/lms/templates/courseware/testcenter_exam.html +++ b/lms/templates/courseware/testcenter_exam.html @@ -78,6 +78,7 @@ // TBD... if (remaining_secs <= 0) { return "00:00:00"; + // do we just set window.location = value? } // count down in terms of hours, minutes, and seconds: @@ -104,19 +105,41 @@ - +
    +
    +
    Time Remaining:
     
    +
    +
    + +% if accordion: + <%include file="/courseware/course_navigation.html" args="active_page='courseware'" /> +% endif - 
    +
    + +% if accordion: +
    +
    + close +
    + + +
    +% endif +
    ${content}
    - % if course.show_calculator:
    Calculator diff --git a/lms/urls.py b/lms/urls.py index 021079333a..f6819d05a2 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -220,6 +220,12 @@ if settings.COURSEWARE_ENABLED: # timed exam: url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/timed_exam/(?P[^/]*)/(?P
    [^/]*)/$', 'courseware.views.timed_exam', name="timed_exam"), + # (handle hard-coded 6.002x exam explicitly as a timed exam, but without changing the URL. + # not only because Pearson doesn't want us to change its location, but because we also include it + # in the navigation accordion we display with this exam (so students can see what work they have already + # done). Those are generated automatically using reverse(courseware_section). + url(r'^courses/(?PMITx/6.002x/2012_Fall)/courseware/(?PFinal_Exam)/(?P
    Final_Exam_Fall_2012)/$', + 'courseware.views.timed_exam'), #Inside the course url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/$', From d0d02963107c73249011d99fc09a88bc5b506ee1 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 1 Feb 2013 19:57:17 -0500 Subject: [PATCH 036/444] studio - revising course-based navigation - initial scss setup --- cms/static/img/logo-edx-studio.png | Bin 0 -> 2461 bytes cms/static/sass/_extends.scss | 78 +++++++++++++++++++++++++++++ cms/static/sass/base-style.scss | 1 + cms/templates/base.html | 2 +- common/static/sass/_mixins.scss | 6 +-- 5 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 cms/static/img/logo-edx-studio.png create mode 100644 cms/static/sass/_extends.scss diff --git a/cms/static/img/logo-edx-studio.png b/cms/static/img/logo-edx-studio.png new file mode 100644 index 0000000000000000000000000000000000000000..006194a195d546fb40564b6bb07cf926c25cff7a GIT binary patch literal 2461 zcma)7dpK0x9^NjwjC>dpVj7x}M4a4(lItK%BzKKta*vREBE&E=W{e5rk}x8Cd?gGs zlf>sv;S(xyJt7h!Q8~La=W)(+{yO_v``zz)*ZceZ*7`l`d3GAw)^61lR#aD z+ynJ1g_XPmZ5b!6SjYSuR06@MGzz8cm}9KDZGm?p6A{3I`&OESfqFbrFDZ=#5(d(@ z`h1vX_McwVsreKsrAi_xZpP;-*}f}>CBMars3 z2bSh2SV6CsCx=E5Gsx*U{I9DU{3t?3S(@C-LI!|$wHe_D_CE}!Ovt(??Emn1NU_|5 zH(~*0P(OM)ksNvS6sJqz(R^x965!U`Z`#?~d#r%Z_=nb&GYh|-?KyN&@Yc@Rbt=Mc z>xV8RX4$i2Fs!0@=S&nWB(B90eHWskm+G!SaJ{67Xg-`%z8HSBOXVGn<@stf`R7{g z&uM$Pdc(*HZ$0y%4HvB8oR)avCW1G(F3h3Mt}RZHVlB(s?v3Z0lsIM0`>^Dj=&00T zCBCh0;83Q<`_OA(LBiiw@$hHk#UjN`He0&P-%5HI@TCr@L~jXZGUMnIcA8 zEi|3rNhpt)Q+BiI?038xYpSF^ITDzfs8$sMgBr=2Kc1K`VvD1XI>f-RQQ=c`t>1^VG7UXL~?Yq@1XXUCQr z&ipZAv6kSRys8T;Nl`GT6b!b6U-I-Ee6FWKxONJbr?2f7T2&7=+H&F5 zzc}?V*;v$&Y}bM{k8xw9Fuojo)fHSL^R#P~E+USRf58#~E-DasFJ)uP^pX55Xajm% zVD8$ZE1@`qLAtuobd$$TyV8@79yB9%Q&w1;&rSTVr}vx6Y7*%_#>ukyk~zR6s9)yv z&-IUYtd_OTgwfsY+!Aa;7tCUOzhpDr6|@61ntPW<++8G7+AOwyu)>CeE8nQaci~Oe zcb$>bMCcOZmQ#DGdGIXVtFzmlYq;jepm6bZ*V`3+fkMgOgCtQ>*$E$7XZdPB$J^Uo zme|?N(7o4(Ysd$)I_qrVLV$@$Ww8qA>Idr@oA-Ks){mYt>KRK9Bv9)KXC#5%~^ zlAwuI%dVDl#9`wn9{V0d*D#9}B$VSsZBks2skLxC8|ptji|{y%5q)jUl~%%!6;y&K z)Fe*%)SpS&z&Q;Oc1;aU^ql`f@-IB|K92=5xV%%3712D&cS(^mz(8<1n-{P>1 z8WvPuL>DFJdM^EHgk;z#1Gk;4Hvr}JS$R`mwGL5_b{ScOM0(bJGvvZFPu-5GOP>=X zSL-5?4Gr`;g+&-?(a@~XMhE}x+QRZAd-r+1&Y-2ETVD(Ft8FR&&>||YH>dT0cEYXS zQ1tJmwB&Bj!d*u$#j?#o;rV%2qgoqfz87`0yqVUd-#173ZIlpW&=S`83Yw!5B7#9hsrE1Bj>&agdyBAGqI8lR z12mjp#;Z%+DHp%HAW+2I&h|p_yf|{mL3_O$3<>MV7^u-reqTQ#{eiZThp5Jn8beq47#NK literal 0 HcmV?d00001 diff --git a/cms/static/sass/_extends.scss b/cms/static/sass/_extends.scss new file mode 100644 index 0000000000..5907481bd1 --- /dev/null +++ b/cms/static/sass/_extends.scss @@ -0,0 +1,78 @@ +.faded-hr-divider { + @include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%, + rgba(200,200,200, 1) 50%, + rgba(200,200,200, 0))); + height: 1px; + width: 100%; +} + +.faded-hr-divider-medium { + @include background-image(linear-gradient(180deg, rgba(240,240,240, 0) 0%, + rgba(240,240,240, 1) 50%, + rgba(240,240,240, 0))); + height: 1px; + width: 100%; +} + +.faded-hr-divider-light { + @include background-image(linear-gradient(180deg, rgba(255,255,255, 0) 0%, + rgba(255,255,255, 0.8) 50%, + rgba(255,255,255, 0))); + height: 1px; + width: 100%; +} + +.faded-vertical-divider { + @include background-image(linear-gradient(90deg, rgba(200,200,200, 0) 0%, + rgba(200,200,200, 1) 50%, + rgba(200,200,200, 0))); + height: 100%; + width: 1px; +} + +.faded-vertical-divider-light { + @include background-image(linear-gradient(90deg, rgba(255,255,255, 0) 0%, + rgba(255,255,255, 0.6) 50%, + rgba(255,255,255, 0))); + height: 100%; + width: 1px; +} + +.vertical-divider { + @extend .faded-vertical-divider; + position: relative; + + &::after { + @extend .faded-vertical-divider-light; + content: ""; + display: block; + position: absolute; + left: 1px; + } +} + +.horizontal-divider { + border: none; + @extend .faded-hr-divider; + position: relative; + + &::after { + @extend .faded-hr-divider-light; + content: ""; + display: block; + position: absolute; + top: 1px; + } +} + +.fade-right-hr-divider { + @include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%, + rgba(200,200,200, 1))); + border: none; +} + +.fade-left-hr-divider { + @include background-image(linear-gradient(180deg, rgba(200,200,200, 1) 0%, + rgba(200,200,200, 0))); + border: none; +} \ No newline at end of file diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss index e3463477c1..7fb86bfaaa 100644 --- a/cms/static/sass/base-style.scss +++ b/cms/static/sass/base-style.scss @@ -8,6 +8,7 @@ @import "fonts"; @import "variables"; @import "cms_mixins"; +@import "extends"; @import "base"; @import "header"; @import "dashboard"; diff --git a/cms/templates/base.html b/cms/templates/base.html index 84f10fc2d1..5b25746a0f 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -29,8 +29,8 @@ - + <%static:js group='main'/> <%static:js group='module-js'/> diff --git a/common/static/sass/_mixins.scss b/common/static/sass/_mixins.scss index 58a92d1ee6..375ce20b92 100644 --- a/common/static/sass/_mixins.scss +++ b/common/static/sass/_mixins.scss @@ -7,10 +7,10 @@ @return $body-line-height * $amount; } -@mixin hide-text(){ - text-indent: -9999px; +@mixin hide-text() { + text-indent: -100%; + white-space: nowrap; overflow: hidden; - display: block; } @mixin vertically-and-horizontally-centered ( $height, $width ) { From 83ad950887d96377d01c9444d95ae5e5abb6245c Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 1 Feb 2013 20:48:59 -0500 Subject: [PATCH 037/444] studio - revised course nav: plumbed in current html and scss progress into header --- cms/static/js/base.js | 15 ++ cms/static/sass/_base.scss | 13 +- cms/static/sass/_header.scss | 413 +++++++++++++++++++++++------- cms/static/sass/_variables.scss | 6 + cms/templates/base.html | 4 +- cms/templates/widgets/header.html | 124 ++++++--- common/static/sass/_mixins.scss | 17 +- 7 files changed, 465 insertions(+), 127 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 41c1ee3cdb..7c7e0f2bfd 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -39,6 +39,21 @@ $(document).ready(function() { $('.unit .item-actions .delete-button').bind('click', deleteUnit); $('.new-unit-item').bind('click', createNewUnit); + // nav-related + $('body').addClass('js'); + + $('.nav-dropdown .nav-item .title').click(function(e){ + + $subnav = $(this).parent().find('.nav-sub'); + $title = $(this).parent().find('.title'); + + e.preventDefault(); + $('.nav-dropdown .nav-item .title').removeClass('is-selected'); + $('.nav-dropdown .nav-item .nav-sub').removeClass('is-shown'); + $title.toggleClass('is-selected'); + $subnav.toggleClass('is-shown'); + }); + // toggling overview section details $(function(){ if($('.courseware-section').length > 0) { diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index 6e5c547b87..7c017ab76e 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -6,7 +6,7 @@ body { min-width: 980px; - background: rgb(240, 241, 245); + background: $m-gray-l; font-size: 16px; line-height: 1.6; color: $baseFontColor; @@ -42,6 +42,12 @@ h1 { margin-bottom: 30px; } +.wrapper { + @include clearfix(); + @include box-sizing(border-box); + width: 100%; +} + .main-wrapper { position: relative; margin: 0 40px; @@ -417,4 +423,9 @@ body.show-wip { font-size: 20px; color: rgba(0, 0, 0, 0.85); } +} + +// basic utility +.sr { + @include text-sr(); } \ No newline at end of file diff --git a/cms/static/sass/_header.scss b/cms/static/sass/_header.scss index e031e16f50..9c4b10b0b7 100644 --- a/cms/static/sass/_header.scss +++ b/cms/static/sass/_header.scss @@ -1,109 +1,348 @@ -body.no-header { - .primary-header { - display: none; - } +// header +.wrapper-header { + margin: 0 0 ($baseline*1.5) 0; + padding: $baseline; + border-bottom: 1px solid tint($m-gray, 50%); + @include box-shadow(0 1px 5px 0 rgba(0,0,0, 0.1)); + background: $white; + height: 76px; + position: relative; + width: 100%; + z-index: 10; + + a { + color: $baseFontColor; + display: block; + + &:hover, &:active { + color: $blue; + } + } } -@mixin active { - @include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0)); - background-color: rgba(255, 255, 255, .3); - @include box-shadow(0 -1px 0 rgba(0, 0, 0, .2) inset, 0 1px 0 #fff inset); - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); +.header { + @include clearfix(); + max-width: 1280px; + margin: 0 auto; + color: $lightGrey; } -.primary-header { - width: 100%; - margin-bottom: 30px; +.branding, .info-course, .nav-course, .nav-account { + display: inline-block; + vertical-align: top; +} - &.active-tab-courseware #courseware-tab { - @include active; - } +.branding, .info-course, .nav-course { + margin: 0 ($baseline*0.75) 0 0; +} - &.active-tab-assets #assets-tab { - @include active; - } - - &.active-tab-pages #pages-tab { - @include active; - } +.branding { + position: relative; + padding-right: 15px; - &.active-tab-users #users-tab { - @include active; - } + a { + @include text-hide(); + display: block; + width: 164px; + height: 32px; + background: transparent url('../img/logo-edx-studio.png') 0 0 no-repeat; + } - &.active-tab-settings #settings-tab { - @include active; - } + &:before { + @extend .faded-vertical-divider; + content: ""; + display: block; + height: 50px; + position: absolute; + right: 1px; + top: -8px; + width: 1px; + } - &.active-tab-import #import-tab { - @include active; - } + &:after { + @extend .faded-vertical-divider-light; + content: ""; + display: block; + height: 50px; + position: absolute; + right: 0px; + top: -12px; + width: 1px; + } +} - &.active-tab-export #export-tab { - @include active; - } +.info-course { + position: relative; + max-width: 500px; + min-width: 200px; + width: 23%; + padding-right: 15px; + font-size: 14px; - .drop-icon { - margin-left: 5px; - font-size: 11px; - } + &:before { + @extend .faded-vertical-divider; + content: ""; + display: block; + height: 50px; + position: absolute; + right: 1px; + top: -8px; + width: 1px; + } - .settings-icon { - font-size: 18px; - line-height: 18px; - } + &:after { + @extend .faded-vertical-divider-light; + content: ""; + display: block; + height: 50px; + position: absolute; + right: 0px; + top: -12px; + width: 1px; + } - .class-nav-bar { - clear: both; - @include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0)); - background-color: $lightBluishGrey; - @include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset); - } + .course-number, .course-title { + display: block; + } - .class-nav { - @include clearfix; + .course-number { + font-size: 12px; + } - a { - float: left; - display: inline-block; - padding: 15px 25px 17px; - font-size: 15px; - color: #3c3c3c; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3); + .course-title { + width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-size: 16px; + font-weight: 600; - &:hover { - @include linear-gradient(top, rgba(255, 255, 255, .2), rgba(255, 255, 255, 0)); - } - } + } +} - li { - float: left; - } - } +.nav-course { + width: 275px; + margin-top: -($baseline/4); + font-size: 14px; - .class { - @include clearfix; - height: 100%; - font-size: 12px; - color: rgb(163, 171, 184); - @include linear-gradient(top, rgba(255, 255, 255, 0), rgba(255, 255, 255, .1)); - background-color: rgb(47, 53, 63); + ol { + @include clearfix(); + } - a { - display: inline-block; - height: 20px; - padding: 5px 10px 6px; - color: rgb(163, 171, 184); - } + .nav-item { + display: inline-block; + vertical-align: bottom; + margin: 0 ($baseline/2) 0 0; - .home { - position: relative; - top: 1px; - } + &:last-child { + margin-right: 0; + } - .log-out { - position: relative; - top: 3px; - } - } + .title { + display: block; + padding: 5px; + text-transform: uppercase; + + &:hover, &:active { + color: $blue; + } + + .label-prefix { + display: block; + font-size: 11px; + } + + .ss-icon { + + } + + &.is-selected { + color: $blue; + } + } + + // current state + &.is-current { + + .title { + color: $blue; + } + } + + // specific nav items + &.nav-course-courseware { + } + + &.nav-course-settings { + } + + &.nav-course-tools { + } + } +} + +.nav-account { + float: right; + max-width: 300px; + min-width: 175px; + width: 20%; + margin-top: 10px; + font-size: 14px; + text-align: right; + + .nav-account-username { + .ss-icon { + display: inline-block; + vertical-align: middle; + margin-right: 3px; + font-size: 12px; + } + + .account-username { + display: inline-block; + width: 80%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } +} + +// dropdown UI +.nav-dropdown { + + .nav-item { + position: relative; + } + + .nav-sub { + @include border-radius(2px); + position: absolute; + top: 30px; + width: 125px; + border: 1px solid tint($m-gray, 50%); + padding: ($baseline/4) ($baseline/2); + @include box-shadow(0 1px 5px 0 rgba(0,0,0, 0.1)); + background: $white; + + li { + margin: 0 0 ($baseline/4) 0; + border-bottom: 1px solid tint($m-gray, 75%); + padding: 0 0($baseline/4) 0; + font-size: 13px; + + &:last-child { + margin-bottom: 0; + border-bottom: none; + padding-bottom: 0; + } + + a { + display: block; + } + } + + // arrows + &:after, &:before { + + } + + &:after { + + } + + &:before { + + } + } + + // vendor + .arrow_box { + position: relative; + background: #fff; + border: 1px solid #a1a1a1; + } + .arrow_box:after, .arrow_box:before { + bottom: 100%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + } + + .arrow_box:after { + border-color: rgba(255, 255, 255, 0); + border-bottom-color: #fff; + border-width: 10px; + left: 50%; + margin-left: -10px; + } + .arrow_box:before { + border-color: rgba(161, 161, 161, 0); + border-bottom-color: #a1a1a1; + border-width: 11px; + left: 50%; + margin-left: -11px; + } + + // specific navs + &.nav-account { + + .nav-sub { + right: 0; + text-align: left; + } + } + + &.nav-course { + + .nav-sub { + left: -5px; + } + + .nav-course-courseware { + + .nav-sub { + top: 50px; + } + } + + .nav-course-settings { + + .nav-sub { + top: 50px; + } + } + + .nav-course-tools { + + .nav-sub { + + } + } + } +} + +// js enabled +.js { + + .nav-dropdown { + + .nav-item .title { + cursor: pointer; + } + } + + .nav-sub { + @include transition (opacity 1.0s ease-in-out 0s); + opacity: 0; + pointer-events: none; + + &.is-shown { + opacity: 1.0; + pointer-events: auto; + } + } } \ No newline at end of file diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index a783abeaeb..18f2f5b22d 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -1,3 +1,5 @@ +$baseline: 20px; + $gw-column: 80px; $gw-gutter: 20px; @@ -35,3 +37,7 @@ $disabledGreen: rgb(124, 206, 153); $darkGreen: rgb(52, 133, 76); $lightBluishGrey: rgb(197, 207, 223); $lightBluishGrey2: rgb(213, 220, 228); + +// new colors - progress for re-org +$m-gray-l: rgb(247, 247, 247); +$m-gray: rgb(67,67,67); diff --git a/cms/templates/base.html b/cms/templates/base.html index 5b25746a0f..7c51fd15e2 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -9,8 +9,8 @@ <%static:css group='base-style'/> - + <%block name="title"></%block> @@ -29,8 +29,8 @@ - + <%static:js group='main'/> <%static:js group='module-js'/> diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 5f41452339..d0f172ad55 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -1,40 +1,94 @@ <%! from django.core.urlresolvers import reverse %> -<% active_tab_class = 'active-tab-' + active_tab if active_tab else '' %> -
    -
    -
    -
    - % if context_course: - <% ctx_loc = context_course.location %> - › - ${context_course.display_name} › - % endif -
    - -
    - ${ user.username } - % if user.is_authenticated(): - - % else: - Log in - % endif -
    -
    -
    - -
    + + % if user.is_authenticated(): + + % else: + + % endif + +
    \ No newline at end of file diff --git a/common/static/sass/_mixins.scss b/common/static/sass/_mixins.scss index 375ce20b92..a10d4bdcdd 100644 --- a/common/static/sass/_mixins.scss +++ b/common/static/sass/_mixins.scss @@ -7,12 +7,25 @@ @return $body-line-height * $amount; } -@mixin hide-text() { +// image-replacement hidden text +@mixin text-hide() { text-indent: -100%; white-space: nowrap; overflow: hidden; } +// hidden elems - screenreaders +@mixin text-sr() { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + @mixin vertically-and-horizontally-centered ( $height, $width ) { left: 50%; margin-left: -$width / 2; @@ -21,4 +34,4 @@ min-width: $width; position: absolute; top: 150px; -} +} \ No newline at end of file From 22444f6dfe94c9dfbfc075e4ea6849f14b124e19 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 1 Feb 2013 20:50:29 -0500 Subject: [PATCH 038/444] studio - revised course nav: fixed placeholder login link text --- cms/templates/widgets/header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index d0f172ad55..e420c7e661 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -85,7 +85,7 @@

    You're not currently logged in

    1. - +
    From fcbd4bd1cd5ed1b83cd000f3f58fa4d45beeddcc Mon Sep 17 00:00:00 2001 From: Kevin Chugh Date: Sat, 2 Feb 2013 05:46:11 -0500 Subject: [PATCH 039/444] produce and consume group_id from dropdowns --- .../django_comment_client/base/views.py | 23 +++++++++++-------- .../django_comment_client/forum/views.py | 2 -- lms/djangoapps/django_comment_client/utils.py | 2 +- lms/templates/discussion/_new_post.html | 2 +- .../discussion/_underscore_templates.html | 2 +- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index ce53a8efbb..604fe60282 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -86,27 +86,32 @@ def create_thread(request, course_id, commentable_id): 'anonymous_to_peers' : anonymous_to_peers, 'commentable_id' : commentable_id, 'course_id' : course_id, - 'user_id' : request.user.id, + 'user_id' : request.user.id }) user = cc.User.from_django_user(request.user) + #kevinchugh because the new requirement is that all groups will be determined + #by the group id in the request this all goes away + # Cohort the thread if the commentable is cohorted. - if is_commentable_cohorted(course_id, commentable_id): - user_group_id = get_cohort_id(user, course_id) + #if is_commentable_cohorted(course_id, commentable_id): + # user_group_id = get_cohort_id(user, course_id) # TODO (vshnayder): once we have more than just cohorts, we'll want to # change this to a single get_group_for_user_and_commentable function # that can do different things depending on the commentable_id - if cached_has_permission(request.user, "see_all_cohorts", course_id): + # if cached_has_permission(request.user, "see_all_cohorts", course_id): # admins can optionally choose what group to post as - group_id = post.get('group_id', user_group_id) - else: + # group_id = post.get('group_id', user_group_id) + # else: # regular users always post with their own id. - group_id = user_group_id - thread.update_attributes(group_id=group_id) - + # group_id = user_group_id + if post['group_id']: + thread.update_attributes(group_id=post['group_id']) + thread.save() + print thread if post.get('auto_subscribe', 'false').lower() == 'true': user = cc.User.from_django_user(request.user) user.follow(thread) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index c92324cbbb..fa07394c90 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -178,8 +178,6 @@ def forum_form_discussion(request, course_id): 'is_course_cohorted': is_course_cohorted(course_id) } # print "start rendering.." - print "\n\n\n\n*******************************" - print context return render_to_response('discussion/index.html', context) @login_required diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index 3c9669ac37..07d8ef9660 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -374,7 +374,7 @@ def safe_content(content): 'updated_at', 'depth', 'type', 'commentable_id', 'comments_count', 'at_position_list', 'children', 'highlighted_title', 'highlighted_body', 'courseware_title', 'courseware_url', 'tags', 'unread_comments_count', - 'read', + 'read', 'group_id' ] if (content.get('anonymous') is False) and (content.get('anonymous_to_peers') is False): diff --git a/lms/templates/discussion/_new_post.html b/lms/templates/discussion/_new_post.html index 5b55d409df..a009f19350 100644 --- a/lms/templates/discussion/_new_post.html +++ b/lms/templates/discussion/_new_post.html @@ -52,7 +52,7 @@
    Make visible to: -
    -
    - - -
    -
    - - or - -
    - - +
    +
    +
    +
    + +
    + Required Information to Sign into edX Studio + +
      +
    1. + + +
    2. + +
    3. + Forgot password? + + +
    4. +
    +
    + +
    + +
    + + + +
    +
    + + + + + + +<%block name="jsextra"> - - + \ No newline at end of file diff --git a/cms/templates/signup.html b/cms/templates/signup.html index 1c542fb437..3ff6ca953e 100644 --- a/cms/templates/signup.html +++ b/cms/templates/signup.html @@ -10,13 +10,13 @@

    Sign Up for edX Studio

    - +

    Ready to start creating online courses? Sign up below and start creating your first edX course today.

    -
    +
    @@ -67,6 +67,9 @@
    + + +
    diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 9f0d5d1cff..62954b8211 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -2,6 +2,11 @@
    - - + + + +% else: +<%block name="title">Welcome to edX Studio +<%block name="bodyclass">not-signedin index howitworks <%block name="content"> -
    -
    -

    My Courses

    -
    - % if user.is_active: - New Course - - % else: -
    -

    - In order to start authoring courses using edX studio, please click on the activation link in your email. -

    -
    - % endif -
    -
    -
    + +
    +
    + +
    +
    +% endif \ No newline at end of file From 69225f643524e56a977331ba3e31a6bb2896efeb Mon Sep 17 00:00:00 2001 From: Kevin Chugh Date: Mon, 4 Feb 2013 08:33:33 -0500 Subject: [PATCH 048/444] cohort ID showing, have to translate to display text --- lms/djangoapps/django_comment_client/forum/views.py | 3 +++ lms/templates/discussion/_underscore_templates.html | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index fa07394c90..2f2809d89a 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -189,6 +189,9 @@ def single_thread(request, course_id, discussion_id, thread_id): try: thread = cc.Thread.find(thread_id).retrieve(recursive=True, user_id=request.user.id) + print("\n\n\n\n\n\n***************************") + print("thread.get('group_id','NONE')") + print(thread) except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err: log.error("Error loading single thread.") raise Http404 diff --git a/lms/templates/discussion/_underscore_templates.html b/lms/templates/discussion/_underscore_templates.html index 9903da4c30..4acb6138b9 100644 --- a/lms/templates/discussion/_underscore_templates.html +++ b/lms/templates/discussion/_underscore_templates.html @@ -26,9 +26,11 @@
    - %if group_id: -
    This post visible only to Group ${cohort_dictionary[thread['group_id']]}.
    - %endif + + ${"<% if (obj.group_id) { %>"} +
    This post visible only to Group ${"<%- obj.group_id%>"}.
    + ${"<% } %>"} + + ${'<%- votes["up_count"] %>'}

    ${'<%- title %>'}

    From ecbe297936d28c8ca326d70b1c131428c8073ab7 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 4 Feb 2013 09:39:36 -0500 Subject: [PATCH 049/444] studio - soft landing UI: how it works pathing, html and basic layout styling --- cms/djangoapps/contentstore/views.py | 2 + cms/static/img/pl-1x1-000.png | Bin 0 -> 924 bytes cms/static/img/pl-1x1-fff.png | Bin 0 -> 925 bytes cms/static/sass/_footer.scss | 2 +- cms/static/sass/_header.scss | 2 +- cms/static/sass/_index.scss | 252 ++++++++++++++++++++------- cms/static/sass/base-style.scss | 1 + cms/templates/howitworks.html | 139 +++++++++++++++ cms/templates/widgets/footer.html | 2 +- cms/urls.py | 1 + 10 files changed, 338 insertions(+), 63 deletions(-) create mode 100644 cms/static/img/pl-1x1-000.png create mode 100644 cms/static/img/pl-1x1-fff.png create mode 100644 cms/templates/howitworks.html diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 14f96e312a..d8090cf68b 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -93,6 +93,8 @@ def login_page(request): 'forgot_password_link': "//{base}/#forgot-password-modal".format(base=settings.LMS_BASE), }) +def howitworks(request): + return render_to_response('howitworks.html', {}) # ==== Views for any logged-in user ================================== diff --git a/cms/static/img/pl-1x1-000.png b/cms/static/img/pl-1x1-000.png new file mode 100644 index 0000000000000000000000000000000000000000..b94b7a97467528a7b57b62a189428888464b2d0b GIT binary patch literal 924 zcmaJ=&yJHo9A;e)>n7`cJxy2-YAi6cU^^*pxC0!UCrb2u4=1K3q zgD>F0v#;R62k_!6IMXd%4^{%q%rD>f{r=8}e(%-U>7!FglFqzqXTaAZ@txe`|4+s6 zCtuE)J7z;NV?{u*)QQLx11|~Ycz}aw{q{Ro*z1ktM$|e9g~nyoj$i6c0DOQMg%#tw_Fn3ECy%0tsdTD3hBs zN2MiibrIhSvnqovge@)kHmI@R2R5M?7)resY7hd`RA9p}P52z>8q`&dzh(_KkYOVI z1=u|@_eP@`8aUmZFMhJ*1!Eaf)uJerqOK4+S7EEw5*oT*d zIAbwMflv&lWW_9*M|zt=lI>;Fd{-u3FtrRa6)KuYsRH``|Dj2;N9SyS@8bQZa6VdR zSRLS;tZ2v^H@gt7GGtR6FhWO!+*DQUF9;*~f@HuRn&62agmEeiPqz%;M_!tN4M)u+wB?+S8wVrthf#dR|!s8#f|Q`je}e<3`xd4 zJDA4raMYzF0o$Zed^i?(P;bYL4#%P&?Ws}klGHlgrg{lL`EF@(paf|=6u0UCdAf_ zU&0^gz{J494`ARYz{=Ecm&7Rp!LogKuioc*pZDXgd)=4EM~{yLK{$3V>^@r$`1jzD z{Xgb|A8dI@23C(?igm#F>2)MC-iTcP77jM6!rXbvV5)DUm-HFMd}ALM8OzcMu{o@y7?@EC^W^_x>xa13r(YoB|(E_cNi>ZK_e7f&q1?*j37qT z2St39WUw&BH7{g)ZdOFFcA+y<+=v=^Jz!yifG$_1prWe4Fl4o+8;1G}XcbjcD(p2R zwFY$qYG+{kh%6cjC$MjKwz1gB6sMG?P*L(cm-DKO$)%z;8V$~&X%h30vPDAuLQ1le zlEFq|4Jf+gn^ZxG|$2;$g_R*c* ze-dZIMT(R@%J7^7tZ|bQ9x8jv({ptM8hd(4u9rXYJ literal 0 HcmV?d00001 diff --git a/cms/static/sass/_footer.scss b/cms/static/sass/_footer.scss index f15eb1c54a..66a9ce0e95 100644 --- a/cms/static/sass/_footer.scss +++ b/cms/static/sass/_footer.scss @@ -5,7 +5,7 @@ position: relative; width: 100%; - footer { + footer.primary { @include clearfix(); @include font-size(13); max-width: $fg-max-width; diff --git a/cms/static/sass/_header.scss b/cms/static/sass/_header.scss index 518c4bb701..b65d9098e4 100644 --- a/cms/static/sass/_header.scss +++ b/cms/static/sass/_header.scss @@ -19,7 +19,7 @@ } } - header { + header.primary { @include clearfix(); max-width: $fg-max-width; min-width: $fg-min-width; diff --git a/cms/static/sass/_index.scss b/cms/static/sass/_index.scss index a3e210b558..e80f2727ef 100644 --- a/cms/static/sass/_index.scss +++ b/cms/static/sass/_index.scss @@ -1,78 +1,210 @@ -body.index { - > header { - display: none; - } +// how it works/not signed in index +.index { - > h1 { - font-weight: 300; - color: lighten($dark-blue, 40%); - text-shadow: 0 1px 0 #fff; - -webkit-font-smoothing: antialiased; - max-width: 600px; - text-align: center; - margin: 80px auto 30px; - } + &.not-signedin { - section.main-container { - border-right: 3px; - background: #FFF; - max-width: 600px; - margin: 0 auto; - display: block; - @include box-sizing(border-box); - border: 1px solid lighten( $dark-blue , 30% ); - @include border-radius(3px); - overflow: hidden; - @include bounce-in-animation(.8s); + .wrapper-header { + margin-bottom: 0; + } - header { - border-bottom: 1px solid lighten($dark-blue, 50%); - @include linear-gradient(#fff, lighten($dark-blue, 62%)); - @include clearfix(); - @include box-shadow( 0 2px 0 $light-blue, inset 0 -1px 0 #fff); - text-shadow: 0 1px 0 #fff; + .wrapper-footer { - h1 { - font-size: 14px; - padding: 8px 20px; - float: left; - color: $dark-blue; - margin: 0; - } - - a { - float: right; - padding: 8px 20px; - border-left: 1px solid lighten($dark-blue, 50%); - @include box-shadow( inset -1px 0 0 #fff); - font-weight: bold; - font-size: 22px; - line-height: 1; - color: $dark-blue; + footer.primary { + border: none; + margin-top: 0; + padding-top: 0; } } - ol { - list-style: none; + .wrapper-content-header, .wrapper-content-features, .wrapper-content-cta { margin: 0; - padding: 0; + padding: 0 $baseline; + position: relative; + width: 100%; + } - li { - border-bottom: 1px solid lighten($dark-blue, 50%); + .content { + @include clearfix(); + @include font-size(16); + max-width: $fg-max-width; + min-width: $fg-min-width; + width: flex-grid(12); + margin: 0 auto; + color: $gray-d2; + + header { - a { - display: block; - padding: 10px 20px; + } - &:hover { - color: $dark-blue; - background: lighten($yellow, 10%); - text-shadow: 0 1px 0 #fff; + h2 { + + } + + h3 { + + } + + h4 { + + } + } + + // welcome content + .wrapper-content-header { + padding-bottom: ($baseline*3); + padding-top: ($baseline*3); + background: $blue; + } + + .content-header { + text-align: center; + color: $white; + + h1 { + @include font-size(52); + float: none; + margin: 0 0 ($baseline/2) 0; + border-bottom: 1px solid $blue-l1; + padding: 0; + font-weight: 600; + } + + .tagline { + @include font-size(24); + margin: 0; + color: $white; + } + } + + // feature content + .wrapper-content-features { + padding-bottom: ($baseline*2); + padding-top: ($baseline*2); + background: $white; + } + + .content-features { + + .list-features { + + } + + // indiv features + .feature { + @include clearfix(); + margin: 0 0 ($baseline*2) 0; + border-bottom: 1px solid $gray-l4; + padding: 0 0 ($baseline*2) 0; + + .img { + float: left; + width: flex-grid(3, 12); + margin-right: flex-gutter(); + + img { + display: block; + width: 100%; + height: 100%; + background: $black; + } + } + + .copy { + float: left; + width: flex-grid(8, 12); + margin-top: -($baseline/4); + + h3 { + margin: 0 0 ($baseline) 0; + @include font-size(24); + font-weight: 600; + } + + > p { + @include font-size(18); + } + + .list-proofpoints { + @include clearfix(); + @include font-size(14); + width: flex-grid(8, 8); + margin: ($baseline*1.5) 0 0 0; + + .proofpoint { + float: left; + width: flex-grid(3, 8); + margin-right: flex-gutter(); + + &:last-child { + margin-right: 0; + } + + .title { + @include font-size(16); + margin: 0 0 ($baseline/4) 0; + font-weight: 600; + } + } + } + } + + &:nth-child(even) { + + .img { + float: right; + margin-right: 0; + margin-left: flex-gutter(); + } + + .copy { + float: right; + text-align: right; } } &:last-child { - border-bottom: none; + margin-bottom: 0; + border: none; + padding-bottom: 0; + } + } + } + + // call to action content + .wrapper-content-cta { + padding-bottom: ($baseline*2); + padding-top: ($baseline*2); + background: $white; + } + + .content-cta { + + .list-actions { + + li { + width: flex-grid(6, 12); + margin: 0 auto; + } + + .action { + display: block; + width: 100%; + text-align: center; + } + + .action-primary { + @include blue-button; + @include transition(all .15s); + @include font-size(18); + padding: ($baseline*0.75) ($baseline/2); + font-weight: 600; + text-align: center; + text-transform: uppercase; + } + + .action-secondary { + @include font-size(14); + margin-top: ($baseline/2); } } } diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss index f77b5ca15b..dceac4233d 100644 --- a/cms/static/sass/base-style.scss +++ b/cms/static/sass/base-style.scss @@ -30,6 +30,7 @@ @import "alerts"; @import "login"; @import "account"; +@import "index"; @import 'jquery-ui-calendar'; @import 'content-types'; diff --git a/cms/templates/howitworks.html b/cms/templates/howitworks.html new file mode 100644 index 0000000000..8f7349d476 --- /dev/null +++ b/cms/templates/howitworks.html @@ -0,0 +1,139 @@ +<%inherit file="base.html" /> +<%! from django.core.urlresolvers import reverse %> + +<%block name="title">Welcome to edX Studio +<%block name="bodyclass">not-signedin index howitworks + +<%block name="content"> + +

    +
    +
    +

    Welcome to edX Studio

    +

    Studio helps manage your courses online, so you can focus on teaching them

    +
    +
    +
    + +
    +
    +
    +

    Studio's Many Features

    +
    + +
      +
    1. +
      + Studio Helps You Keep Your Courses Organized +
      Studio Helps You Keep Your Courses Organized
      +
      + +
      +

      Keeping Your Course Organized

      +

      The backbone of your course is how it is organized. Studio offers an Outline Mode, providing a simple hierarchy and easy drag and drop to help you and your students stay organized.

      + +
        +
      • +

        Simple Organization For Content

        +

        Studio uses Sections and Learning Sequences to organize your content into a simple hierarchy.

        +
      • + +
      • +

        Change Your Mind Anytime

        +

        Draft your outline and build content anywhere. Simple drag and drop tools let your reorganize quickly.

        +
      • + +
      • +

        Go A Week Or A Semester At A Time

        +

        Build and release Sections to your students incrementally. You don't have to have it all done at once.

        +
      • +
      +
      +
    2. + +
    3. +
      + Studio Keeps Your Learning Sequences and Lectures, Together +
      Studio Keeps Your Learning Sequences and Lectures, Together
      +
      + +
      +

      Learning Sequences: Lectures and Exercises, Together

      +

      The heart of the student experience is being immersed in Learning Sequences — short video lectures interleaved with exercises. Studio allows you to insert videos and author a wide variety of exercise types with just a few clicks.

      + +
        +
      • +

        Create Learning Pathways

        +

        Help your students understand a small interactive piece at a time. Learning Sequences are built from Learning Units.

        +
      • + +
      • +

        Work Visually, Organize Quickly

        +

        Work visually and see exactly what your students will see. Reorganize your Learning Units with drag and drop.

        +
      • + +
      • +

        A Broad Library of Problem Types

        +

        It's more than just multiple choice. Studio has nearly a dozen types of problems to challenge your learners.

        +
      • +
      +
      +
    4. + +
    5. +
      + Studio Gives You Simple, Fast, and Incremental Publishing. With Friends. +
      Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.
      +
      + +
      +

      Simple, Fast, and Incremental Publishing. With Friends.

      +

      Studio works like web applications you already know, yet understands how you build curriculum. Instant publishing to the web when you want it, incremental release when it makes sense. And with co-authors, you can have a whole team building a course, together.

      + +
        +
      • +

        Instant Changes

        +

        Caught a bug? No problem. When you want, your changes to live when you hit Save.

        +
      • + +
      • +

        Release-On Date Publishing

        +

        When you've finished a Section, pick when you want it to go live and Studio takes care of the rest. Build your course incrementally.

        +
      • + +
      • +

        Work in Teams

        +

        Co-authors have full access to all the same authoring tools. Make your course better through a team effort.

        +
      • +
      +
      +
    6. +
    +
    +
    + +
    +
    +
    +

    Sign Up for Studio Today!

    +
    + + +
    +
    + + +<%block name="jsextra"> + + \ No newline at end of file diff --git a/cms/templates/widgets/footer.html b/cms/templates/widgets/footer.html index 1cd387f7ed..e0db473864 100644 --- a/cms/templates/widgets/footer.html +++ b/cms/templates/widgets/footer.html @@ -1,7 +1,7 @@ <%! from django.core.urlresolvers import reverse %> + + <%block name="jsextra"> \ No newline at end of file From b456281a8f54aeacb6dc0b0919d510a5fb9f4608 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 5 Feb 2013 11:34:28 -0500 Subject: [PATCH 064/444] studio - soft landing/nav: added in functionality/content to soft landing view and started punch list --- cms/static/img/hiw-feature1.png | Bin 260686 -> 196543 bytes cms/static/img/hiw-feature2.png | Bin 0 -> 195814 bytes cms/static/img/hiw-feature3.png | Bin 0 -> 37037 bytes cms/static/img/thumb-hiw-feature2.png | Bin 0 -> 32810 bytes cms/static/img/thumb-hiw-feature3.png | Bin 0 -> 45949 bytes cms/static/sass/_header.scss | 15 +++++- cms/static/sass/_index.scss | 71 ++++++++++++++++++++++++-- cms/static/sass/_modal.scss | 12 +++++ cms/static/sass/_subsection.scss | 8 +++ cms/static/sass/_unit.scss | 4 +- cms/templates/edit_subsection.html | 1 + cms/templates/howitworks.html | 50 ++++++++++++++---- cms/templates/widgets/header.html | 5 +- common/static/sass/_mixins.scss | 2 +- 14 files changed, 146 insertions(+), 22 deletions(-) create mode 100644 cms/static/img/hiw-feature2.png create mode 100644 cms/static/img/hiw-feature3.png create mode 100644 cms/static/img/thumb-hiw-feature2.png create mode 100644 cms/static/img/thumb-hiw-feature3.png diff --git a/cms/static/img/hiw-feature1.png b/cms/static/img/hiw-feature1.png index 381b182a1b90c77a76ca371189e0865ad7358266..c372dc7a8ace0090bb9b9420c7d7f7f213a6d1fb 100644 GIT binary patch literal 196543 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

    ~hBybK`7Ro#mnPmqz>qeknyS$Gh% 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

    xy)3SYYNo50d@as8*yMRnPqap8O4gN;LV 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 literal 260686 zcmagF1yEc~*Z4U&BoIOf?(R0Y6EwKH1$URhB@o=*-8HydaCeu%-Q8L8Jn#Gcw`%v> z?V74-Ip_50bIZv^;eXrpgLC~l=^Vx(ZCXXt7-XvFibMQWy`=BOqu#bsb)MX&c)hTg@> z_FWqQ;Nf?%)iba#awOC@GBLB}B|dBEASN_3DxFt@)Ey$`tK5~Z2!lswZnhH^d2w<7d=}BCVIxdOZu;%wDkYC zsFl_KNIN(x82ulA|6dI|D7o4iF(?>0*f`l6yk9tDlD|{gatYZR={efiE7{ms{x^zp zrZ$c?4yHD?ghGmJgp|^H24>cOJE;EAke22Ww{~#Uvo)3$m)ZaB3)BB4{H>@$pr5>yabbVZew4Aj(u+&*Jc78VvVGM|
    -GCy9^3slyMguwAAs# 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%0ATxhvU5YNtZDl2bYRdftVNBw~u%VNHi>%|dP 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 literal 0 HcmV?d00001 diff --git a/cms/static/img/hiw-feature3.png b/cms/static/img/hiw-feature3.png new file mode 100644 index 0000000000000000000000000000000000000000..4dd1a5f7584af2968e2154471be1f8db1699517e GIT binary patch literal 37037 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 literal 0 HcmV?d00001 diff --git a/cms/static/img/thumb-hiw-feature2.png b/cms/static/img/thumb-hiw-feature2.png new file mode 100644 index 0000000000000000000000000000000000000000..a549c7d2deb9de51d0f2251a9c2c95f91df389f5 GIT binary patch literal 32810 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 A-T(jq literal 0 HcmV?d00001 diff --git a/cms/static/img/thumb-hiw-feature3.png b/cms/static/img/thumb-hiw-feature3.png new file mode 100644 index 0000000000000000000000000000000000000000..24185b323e5880dd6365d7f5df370d2a348e5b6c GIT binary patch literal 45949 zcmbSxb#UFzvZk4t8IGBmnPP~UnVDl|W@hHZjK_A&%nUKJ9aGE{vwgh&-TUs_{o~bc z)vBb@sP*;NJv}{Amo(AJiqgmk1PEYYV92sElB!@};Eewqa1fu82U2{%=Lz3UO507% z$m*wt_srnUe#v$v+roF9+vO zZ7?uF5ie&GGdl}6GE)mH8%H6)Rc9}N%*I>@pv9%Ys^BbPVQnMh<6@!aqo{7?V`s)^ z4iFJ06ZGQ$Byg~BGa>VGuy=Ij_Ywm9iOD3eJzxmb{K zF>^4Pv9hs|adR`XadL5UvoVsfv$C+Ev}#*@8vY!qv&$#q4uDEGhn@{29CdyPlxkKk2gZ z@=3CNk>KLy;o)FolVay%7vmD=V(0uKA<4nc$Hws=U0Fw0HxoxQi~rcQ`Lz3Ax}5)4 zU497{3lldd7j-8m`~PHsvbB?&ldH9pGns@MHyN#hiJ6V#KLWac2I$}Ym9%iN@vt!e z;^O2$_OA%@+x!n6{J(qtZ(Z~MpQy2XI>Yi$di)>h@*mM>4*t{p@8tiy`0wds;rKac zTs|iUxyBD^FtGY+SxGTpp(#J1wP~t&z*8P#{e? z8psoeOyVJ%;d_GRV$+AM6xkq`aNYAPnn?sjmL8?qT7do?iyxqEmnK#=@VDf|Uh2-C z8A)N0tiW%BbawsIKbn#p5}={gu#dykES>%DhH`!(8BbB38U(Y=aw)V35M#>XM+BoD z%h^!zWWBtR&X^u1oa_FpqXuZZij$jA*o(&0XW%7%g|HUZmtU!v?TP> zb>|mtvO63kZ~^`?#RNWH%OA^xwV(!y;i)&{I<6MzyVQ9lP$`lLD`vvaT-oDn71yuE zetlEwV3yrA%Q9NBxk;pi#8{TNYx-s$x9!;A`k7f8+(zagsJ96SM=4-;`P7tgfM>EJ zYpduxYL*e5-H81TD`Cb1KxD;8L*?9q4^=J_HZ=?{E-o%UE`ou+3L_e7_dzgTy)h%S z7o0{RxUvDSc{<8{s&^p|BH%7qT>xo8$nu)l~e6J4NQz){K;W zLwFd8G7+$(m+}gqYLm#F>+*Cyx5_+kIDoa0Mw^~A*Wyhmvt)AiEf?-U+Wcd@F`RNO z+b8Qyl9GjoM{0a)|9IztKgp#7Y*zFTIWI@RoC$xgHZ$I!LR;ZO)iE7!WW?Bf1JI2kgfd0m~49M{F2`7nS0*BWwIJXNa z@{0O2(al41iZDb zuSjf7sp5R*I-QkPHgXUS(|%eQp`tb0L+2Q;a%$6gr&WG|_LS216=}ab=pt*I$;|{E_l)58N7Y?L?js>DLY`y49*M%0XP#cL7cIwqpf8Hn$p>+*! z#mzM24cnsWQIvGTK|(R*CkaNKtONb}&#UsiO{$v6*d4}nv8DpR%q43me@u>_X&zOx zH6U8~=Lex>&N~}3cCX?U^4nIp(Sx6UB%_8 zA=Oy4Fi>R0ailwj{wy7clA&u7pGP+Ilh9GZQ6|o^obn9zzMU z7xsTNo)A9SUQ!@%NHrG2UxFB;R4r`tI z8fd4L`Z0_>ESK3I_MI&faQRD_+O@OVHk!o01R!NYv(6x)7}tjywB_oKG^Nt!#XIKd zEQ3j1SM-q0ISY3JcAglhaLM+9t80S_R zw}wA?;6e9cP$LOL1DD{A(v%zt8+Hg6zP4shFH_kkZ1*u$C`>B+vL_!!tG$rSBvD0& zrQ6w`M>Dp^Hm60H+D~J-)}G$OAV2I%s}(oIRVsI2mkDxs{){n7S2>(YT4EZx=xt?hRzx|nG+a_K zkefU9r9lRMC{F;TsV`2f+YK&^v36^g_Bf+%`p@3f_At)8X(PeZWjW_8zGaqoxWcU& zlGlDr&pe7Ux_%sUX*{%OuF$Mr1YNC#LkhgEWvHRoUO!qEvhH9;4Ga~);3Tcq7X8}j`Xasr7g0qaI}9lovYNa^ zodS|9B;354i6wtROFRcBZA>gP1Op?{N9k+=vv9Nn%p9;nNh;aCV?Ar&^9T+Rg3Pw+kad`@ zC=f=d78mYWGodU;zhsNk`)_DWnPBSAzTq+im|0bSZ0<3sU8m_8C zwgIb}BUxN!X%zn2z#k5et^yv-$_qzNHeeD`b{N(&nTW{HpA-#VTszZEGkR z@~QU^hoOrz|3FivRv-*^LlqZ0L%~8~3~xczF@x31jSw}>$SdaNl*9su6bu`Zg&q4i>6uX72zqWvn7tVAhN}E-%rzS=JEYOtd=HKd=mECEkbT zkn;|vFq#~MG)oekzl40`hR|PJS3IA!ke+UoMFEzUgueI}JH$;)jVW_;rr*Jir!{ej zNfe>21Nj5A>)jL2XK&Eu9cc>R}dOtkgHSW}<`M4q?`ne#HGq}Y6Y;2BJ79eAQ ze@E2NbCpw;9gE>d1ztPPI``=*r(Y}{_22rd!O5)%LScGyOtY{P+HV$~Q1>6yhZN`O z-RLhOKt)xN#*G+H6_*a53c#Y1!RVn|EJuhB;ewxN#c?_Ajde^Tx;xA%N8o#=DvEz0 z!~%=V&_%+VH;E#r#k>3Z8(hH-8qrc39a|TswfU`Qkx6dKS1mFmg9A!DR*HQiNbF;* zrCd=??&h@Sv<&&X;)z*{GHU)XQShpYSr3;JY)FR*+(|}j5sU%67M(J@M_2nxvDJwC zLLZ-{^;#kJ0hG*9J-^GJJGw{T;NlSb7vadUzS%dJNzU9Hw;80Ys|#)lp}D2wJX67Y zoO?f_2KqYOtT=XgrC2dW&~Dvk(WIkh%_q?EnydsDSGuIKg58xLy7%F&4kEE){+{%u zAJ2qqNWo#X6*&{7k$E6i=ivYXEooJI3nEu6fv3S66m7=V3L=g{Sd$o(Sq^6$_5i%&O#r^@pd@u zPbSu_{XD2)nXSE+6o)hj?$R#|!#bFm&@2(tptP-~M__Kn9Su2sEUEfgby}geBx{fY zpead~VRS^2b@34*DKB~z_&)EfU#_C!;?qT~`D&$4+-ZIk12~gI+xZhs@jrp;$n zg+uV+XcQ9p9u{=EW(Hd7TJ(@1%qJ(f*NYtLUYG8do6~92PT_Pci#RWIJ8_{N7CxLz zDj90mI|?ElEF)KI3ccK-h3JGM(-eAwLTu6)KTj6 zQMJBD{ej(G;CnSTqxFLRD$A2ff*{M(%|_7SXtmPEFRNMAh*szyrn|GMC!`^BXG$PY zwiY+I(J;`;z-I=YLy)z{cRE!<=di+mYm)R_QS_?uYtK*nFWHO~ro0s;?#=B@y#d?d zR(Y-k48rsj(7z=qYbt!Wu?DW*UJGcnobEEX^KL6y^4WVktoAVzg>T+Eg%WxmKMw8M zjCHnW0(;vkjk@+;v=HfWjRNNNiB8u~7_)!Zxbbv%cc4~6^}OEyB#Z_{n!`Tw=bQjccGvwEebQ{{gJFg8JItl$r0kK7Q9dghX8d z->ltL+Q2$d*fBL0)qJ_^QX+6rPgRwp!{N0mu$MS$Pf{F3>L85XSkl6O|7F{F$V(qS zGMAgnkyM3}jJL6yLa)%*?gG?Nnpw~}*Zb06DAZj^mtb7<{=V~ZSXqTR(|+BB{nMhL zr=VyyQ=siIdS~aQ2_5(nqEs*6)q9HD8)!jUMaI|m5Nt>4{Zgw_UZdY(>_URzjX-_S z8B3+j=DvMG!<=*Q7)5;5-Cb5`6s(+p)Z=%>1G=9$Qe)|OYi?h2+Wy$mnC;5A#u|e4 ze0zNq@dwKCpPVBeuh1(AUdOIH#=(UcuYpcnTRsK10`u=Fiy2G zf`0~rwnYp#eJ9G9vkmaP4-$4juUiFO6Vc(SJ!BfQ`d%Zy+PSDO%bxZfJ;y`#6Gdzu zhPtesW?$`A9PyL46cdT8{Xw8YAz*+x$R;z7bLHdr&ZSp?TBh8}V1E4L&by2sh-&9; z;(4ZTxwX?2^O&U)ga7hlztBf8y}Z85`T2KFFepiFMF1TfCuy%I*SRFOo4I<2qiv>-F1XJ4E1LORRgj;~>nG zuff`fq#$p;9^xoJ97oKc{M`sONX$~_O`#t+vr^wtMK7%@*zbAIl<0}VQX_wEcrUhl zP`~wU{%t~1ux>0yK*VX}$9ipv>&*Bu6D`g_%!r6vnkK&e*3%TqQ>C4gh?!6Fu67b} zs#f>=LmZ~kM9j#SSjFdME0%o2lf>y0GKNkxfzsD9yyg&Jz3fJgu7_%IAe4^`5OTE(>p@4+q-&Uuy z&~}j?bBlN-kH5{~I~U{3bu-B=_kSOzg)b*Z^BZJT2S%l_!70L5Q?pXPX7%UDJCQ@_ z|3PqvM#myv$3SE>!`5TA8G`-}M%^$u6tGI$s;*mzZ8Y<${#*o?zuH#AbEh;GW7q(A zVs+QiK|js2ii)GvI7oQc=^GOHwM#ZR)9j`dcH*Xj2Y08Ffro%bN@pcx96H1lwo$-e zM4%Is)&163jhpYj*HKPt(0zJ*M!@nilF6K}Ra@?+{Ss5Poh`4TIQCph1zq|a?FE1k zyyqwzaQNoVcSmo@GUfkQOXNvz>uo`^z{AwR8;F5Sr@h{Nv*hczb(}HOA6y{g+~&)x z%J>)3>i)K!YtLFcmtIVcQW*kLOf)90gaF?L602EC?RT=6wB}X_K6d{ct=r*@F1v-o z!b@G*o=@E5S+&1#qAh@aEHm}u50B0dK5eYTiR}rN_diFj{JBQ{=ZUlLpqG`z&eIMh zmefHritqh2AI^h%Bucy(>pP9Ur_q?O5Mc1i>N<%fL)b+XADn0k(UrZtP>@JGi>UXr z!%NtF&uc(yee^DneWXN_XfaExa)xw=fN#uK6TMkcGG^Z4uoQ#9+nNX~lAe?IRwco+ z<^^2&ddzk#y(m5wzAa@uaptE~#6RY73~=@*=#_JUzF7=bs_f=St8oaSzn=e6Au(AY z6vo+!wWwoLcIL&`022JO>`U2-qx{x?Y^B1VM=(Iiqae7Gql*A60U*9RoLdy834tOZ zvE%EgB>qvh_yux&Z0a|M08PNR-Ki@6y9qr-{`fUsv1duoGXyS&0=74rex=}h*F)=<3Ul_q=5Q z&N`uTxzwOjhPFoJcp-C^DZ+oy z`8mCHN~GHR;_gxSy)Qm-#mMK5N6FRYhsEdHBHi&LXv&;4jPV0DGfm!qUXb2ojCKe? zA`CwGWB|wYWDAc2+%0M-Rz=*j2(by8IzCG_Ny1(+naQpH+QT^m4 zgNFRsM@)N9KJ#!5%=Km5nGQ90~-(G&VU(FDQoI#0& z$=TcU0+ty0Ur(VCZ9#r_dhzw)%sm5VZUbRidcu{|u=Fm11nC;zFq7U?-9B2nyC=&I z5F6=ruvG|KZ5bdK@TWyDp~v&}VnSyfR@ZzthXlvjTK1-ix4@-M+)1{>&?vKA?fK_a{wim5`NFg3afjVPnl}aRbSvmyk8QnHjVt8~F`lY6R$(>a1U7 zRD8>zJ@Ju9Omh(9o@C5I4>W+JF~M}lMjwOe?v0FmQse{))v3Qkf|?NRW?x6H5_aA? zueN|bl;3nKL!ZLQb6s*8FTpd-92#A%St3osnRMpU%e=crs(7Wu{$y1v~zhvv}EEKa@NCqKW?_to%Aj($u^>G_e4y?JP=*Y(>2gUt0qrtzE zBJcA;5>J2|ALzqckD55Do$5$Ks6HyOUcde|nfv8<*1;KZ51B=HLx|l(+y)u~H0)Xx z`1m)~Vg?8>c?z{6#Y3;ogCTur6Ofz5Ug=EiT5mUA zhr+wqS|VYrW)wFColIu5?)a}jK1wDUu+QRe?hw)P-^J~}9dgWMr+r*?UPT%Bm9cD+ zrr#db^41r1aK242IEj25{Atd1wNy}zricK_L4iufkL6tiZ#Hs&7ru5?f4nb|c6IK? zInC6Kt&zY>OPS}KoaW1MQtRbnz?G<78oj?&3yD1*$fk?eT;0bY-tzKGmEtFBl}uV_CXP<_qB!dgs27HPb!&1~$nE1%BMOg2jId|ewgbY)>+%+`li z(dRNmuAnHcYNua2H4dUTPiw0}t`1DzudsR)Jzkw;iLwZN550WLF-s5t{#xiheSU0g zM4UkId++o-#L@DC9=x$%XXFh>?|;Heb(5Jo9F-9M3Zw04z=_ zT8vrG?G}TjrbXn)??czJlI4++5#(uuP;5la8D=+Zc^&1w%n=wE7n&Jb4_Q4!UrW2Q zzL2}#Z#Oq;>iftH{hrHRT~3b=Laz_Fi-m6ErMO><$sfbAIm$KM&-Ht47BZGw6pKwC zx*}Z0_p%$^fL3ic7w)+^x;c5?(5dCq%ydk!(a8pcJDb}_`COg-=;yb~Gug^XRJ@G@ zIK*cpHhvdBJw00ErwzUhm(TDG2LrwvIL!Qd$*FS5ESi__t5K`U(=yu4NVYlQ6>8}9 zUwMc>+tBrBAlv|GD`Z6f$k4AG9vahzfG6%@nIx7uxY zC(zmHay(POYoaRgczR@l&#y47ijU^PsDpejqtdyT)Dv4jQG=3_8uLffz@69_ykVj5x zrJbo^>D*x&TTrrMB-lJdZP%RZ*V_(P8YCBCkPyIAEiMyZcIDY;R;@*sFpfeDQdH^W zWS-sgQvO^61+!@>)^VVd5XIW|)%Y}tY!shXheWBknN#8^(1N-k3eiXjknIR4(cYe) z6TFO#8BJ_hl&7X|eDh1zsqXy&pN~zPm^0a(5(XBYjnefvQFU2fECi^KZ}TVPv-kW^ zVo)wueYDO#2rirSA6f^0l+g~4UQEAJP?)S`rk;5QC--UVFPnlj-Q8}i!( zJ~c*y{+31u(~h}(xq2U8B7`_JdKvkthCuvf7{%RXG$dG*mfRWIOdb zUzq&^qD);2s!MPf^WslLt(o>v_&raF;Mwh!ZW^w-qy* z^_ktogw(LpB*B!OUTLI3iNU}H=Yll|uUM`#)fz?13iqUA2g|c0WuFyA%cLF}H?7e3 znz(xomRkjr(eq%yA#*&yVhDk#jM`lO?98@YZ51PUin$beZEMgFb@q{;-M6?Mb5OR| zM||LjBOY+R>uHuyQ}d~6iYKM}*Q1UHX@=iB*O-8lhnRD~B)&lJUhK&so;4pKl^|X$yM$1@Ov^`|rWqg- zFU3P0Z%Ud~Ic`jsy_^&$O0~`~xh8WN$uC%-3D$_=rH|bf-^Ya!I=CEUtlIblVLlgk zWYr~fkgDMXF_-5goQi=z-jAnS3V<_VT`S>7r^q=pAGx=SrD*6Y5?0Ad)Sxbnnfk4q z=CLHjBQ$K;0h8*}*PwSwW(mM323{i}jak&2a63%n!8OuTLqH5w!?1baD)zqRHUWSQ z9R^h8T458b+R7RWUT@@}@mFFXZ}3 zW{~YI7TeG`RKJHcaBx@@4r!;Rp;!A-f`=R#E(lGx%fitV$3kRbOlZeu1TW6^QzmVM z*eev6V_@LsqR{OW%Yn03@f)|Sk{f@mz(c9J2~S7}g^Mqej1LChyxNNbRz%7y6Otc? zsMbv(qD;PgL}>m&9#lUmkkS-bB$l6$Fdn0<7qwKa5k)g*0h&uO0YvYJ$<-7VFwXHB zxZ1OZ_;O}vu>}X>T;NjKebD0_IhlNiTIEWRDfZ^IB~ys1Dl`B6ycTg{SnFHQ&b>;Z#JF0yZSoMfat~WeEb!9B*s}H;!$i8 zGOWq+Tt)uaqCzu-HCbUu;HnN*L_v@?DuMB@4)9#}39|B}S|PC|3LFZWDCWXwyCucO z&01=>2sl>g+On~I{WJkB*jeiIC~^q-u$`*njB__R5H?42Rfc~e-H5U4532G#Lh`mT zZv6*EF^$>utN{Sd7~@v(8itTxzl4l9c+DUD3F>J2nM>zTR3I1RMY81^5xYATnbCc6 zg~UjVvGHWg$f{74C-L%Ah>&IN&6=_qgL=Hs*lglbE{C6Din3+DYfhnbgQm~DB(SUM-(v55w_wc7C; zzYj-I2rDYopFk$47t`2B{A?;WZ$Ph|tI9UjzyJofHSTFVxE0& zHKREN8l9G(q==k;I@sOS!!3k2XyTaYiMe@j!5c#BcnYG5-xgpXsb2f%;G;a{M>tSv zTvSJZYv__^5(+Vk@y?_~O6pOXMA~Zz<&{vvG%=ttiHgc((49^!<&~OemV8tA5KCe} zwvSEyF$T*9#cLVt0K}3PfD@IQ^tg$Qx`}4}z3KQRaO}}u->>=`KM#xb< z0`ONpG);U9S5x<-xPt0M8r89miUm zKH!yJaWv4;#FRAEk}yTcloM$mF5o-OHFgD6amh`)$8Ntz1zgkA7|wvS=_#@$(`w^KcFe5s%-YQ<|G2Ngxz~t6@p4 zU~pXqoc-gNI0Dh7Q`YW!ER^Vb$@oZwzn$q(7&;sup}es;s1^^qECL$UcXhV#x3r1C zEt+$-XHs+wC7os#jeCa$w7f;gg`~s?JW5TICs|-bvWgxyTYz8PGEKF8Q-VA9Q?5eX zE@b1lRg0OMQpzL526=K{zyMZlty2xPkPu8$Bws@S;zEU(H-`V`4Gaj~Ef!CTm=NpM_ayz@$QKLa) zO&m?B^4OI6CyaCp8np>Y_z0oNvTiE9UHC5-iPWvAB%C*=psPjQ)V+#6tco-TR8~9t z%3kI7NP{=rg!=SPAo~hAVq@r6@w|Cy4y~h?Hv55p2~DcMONYU6tXqFlM~X#A4^U4j zn|!vVRmFz!-8nxg6Sc&`KT0JlG$hpL0uQ9tbJ?xcj$W?Xe z?*OG7!taDTUhc1=pou1&mu^y(qi%#2B9JL)UBV^_z}=6atpfY!N44 zJ{tPE`ddDl*HQfN3z$k#mTk*VDSvuWQyP;+>Uw-I+u6zXx(WZ7)LfJBGCo_rfJB$} zzCWP*2@S@Jm{$;jkT_|AFjB^pDq8Sn1uZZE;%cMLcz*24yByed9`o%J6J{5uN09i=U9rvBMHZ0p?)ua|pS>qmd1(*wW; z1PDC$x=zP6pjBaj6Z){3VWb@B;;>1Bz{WGj4)?~+lTc#M%G{ z<#DF!tZx@efsKBO?V9>SHG$ZMcPp=fkE0?Uh*Bwe_u*GN*K2nP@E5*^Wu#jj?qku? zBULzgE`!rNig(RhuCEs#+8ZhdQD5HFuKfQL%NDo5I(6O{*{vi>HrbXjg0__c8mw(A zy;Cer|FS&3_X=EsBDL4&So@8#&d}Yu+gfjScsAXX>{f|MiE>?jdGl;~ZH=7@d|bPz zyTH_aTq-HP4#bP_wjRv3zU@}J=SB_`te<@C`B)%jqIdeZ)MD8%dd=q{eUs}EfV8uy zqacG9`5lE%>yt;32s-KS6}rw$!YE{Q5+h0Asq=m+i`*&nUaK$gyB<9Ru3WusEJz@D z#Ej?!i8okGEA&3wIlpL;Jl#2I&EUVjlU{kZq8@(!=D%Na0`+#1J`KBDq55CXuC%ta z_P!o0TybYAiX2;s^uAXY3Rs96{^_;~4BXi7RC-#!(%kwm(0W|x1szZNt2E(C2o!+s zcXskT9u9Y&XK-VQ=lgjfR2-D04l!rnH@SMdj+J-<&2-KP6Kl4X4ySH2y`-GB)2i!w zTn-^-J7>3DUojOXd%o@L2X-t2KPrvtef)Vf8*VuY1$-|hGn0Zn*jEA-F$MD`|8|w z99~|yBNX%!GH+l|tP_BX@`b?2*Q2Nw^XWDZ?QoeXXD{$5)WB}Vo>`~y&msi*;!@9~ zR?0@tMfb5UEDXR@`te#BF8ySns@I`YWYp;A-&+<#ePGuYZO_B+jfOuAg{Xr zZi?C!9!z2fvtDgh+XlSaRtK)*&IqWb36L88+|o-~Sbmy8G=L0ctF#OB>{WkXst(Lr z)62Gd3;7d;wpZNb7j>xIe4bJ~PKEkJgdFIkl_`HwY+J)8ebXSVw)L6$b@Q3yjA6MNhXM=;3JS8#GSy+4=1sc?7|M`6x`$ewhe~|L8pN@;Joq1*GHkpfxE!2i$n<& z(imAVcN8g@vjv5(yJ+Jo9 z45W||tw>W_k55Sb4y$J`3DPK~0SV$;3XfZVwN9D^X zbe{-YxI?1C&TVqU&wlzx3&C zepYKh!uO#CWrS<2`&h9kPmwGv)gA%+*=4|_@Cz*|r7cquLzJ@st`bAH7 zJ>s{ihC;R=_bF4wRI2H;!}Mpa?M~3u<|%6&EQrnlgM}0vz9(n>nX0U-W}3PC1uqll zezm?4N>tDoQ=TT{I+aN}LxLR8vfm96`{?UP< zic71G`2NGg0U_L$$%ya(L4<+*baBw!V!u z@x3R|xXsBKkHV*`)E%qr+ok{+tQ<|gp{<_W(L5IBpr9j(Z|nl`p=?KgPmVXDkWRJ` z7A|Dj$D9Xy%}Ztp#A{!oPwq9vRLP$U($kBRm{BuEj@^tEeuoi{zg_RFy8F%6acc&q zEkk+KO0yvvfq+}aAEWvUvj?=6Q%>K1Y+1N}*=;qx)oG`rnf8i#$RMFn?3DK821six z)RkObHoUa|pe7_kUH%x^vQQe9A5HF?`Z)@iLs7Ma~EW}W6Y#pvEiv}pxm zx)E@oS}6*_U4~hGE+{y+YJ&VC$%-gHTXO6puES+Q5(4n_nT;WK4u3sRKI4bh}MGrG&viLX4H;a<7{RCpF%{fMG z0$Fa9PIg=|?8T04h#L^L9Q=@#)II*ddZ(Fi-s$_IyWW`q00^?DT%#?i=%^5-NV{~f zK}^k#O%sZQ?x+x=eyW; z@ntJ(=|dM3YrFpVl|kmbL2NcbAkRH;HXatnnA7~%v_Q_GgCV&Cf-<)M8Y`$mh!Ku) zx~bW!>dI1&)fqI4PyX~*7!dnB@_yAa@Xp?;iUJDVr zriMwR8M5I;AF9WBS(zi@heP=-R|(;|pnUGFte#@{T5ha<-(*3Nn{dxmyZZ0&gh{*_QRt@AJ|m zg&?Ljz6v&v8SDIGl|acFl+{fP2C|77H^=idm1xUOEe>r|U;9qH@?rZn?iI!j2i@Pc z@KW$BT8@)qM1F_AP#D5B%6g(xCkFOM=-L`EJm|h`W&q?pXyB?b0=tz@keEl-9Cw33 zAbQW43{F*-&I*fAMa84A)wb#HSpLHRXDQu>;m~#Cg+U*LA3RRCir3(X-MJo}s!iT< zjkvvRZVuKwtL@VkCFO7b9XjJ1^o2h)4`!b#1y3j0;8PJJ(K<2Tlkr1TdkGS)ZEHtj zPg~NUhIDa(exSBxV>qB;n*)H63?aD%&;moF!emv~X$D|2sRL%L?k#mXVHrdpH6g{i zjh1CmvI(+jbuX%0Z5}XxIpPrOPRjI(skqgMV!HwQ;JfL>oaE+r04e?A0?67pnAN#A zIq70aj1P{Tk*3ieYB&%$ri&w{g&r`CtU>}Ykm(8dsR1?|&F02ts!Y+_%gG&0rp+Ar z(054xi>=!@9cYJm!j`Kumvlv4R!`dmJqU=)&2|$lyIzP=ov#@gmhhtLFdZ-vUk*wD z-oE?WK;tPrO%YGa{GE%m$n|aCUbZNrQZ<~ZI)*u8SSiKZ$8d)KkG+(h{K*Kh{0Z6L-ab%^)*_5%)DSn*aNhr%Phon86)>sa;7PX#% zDb^c5{f%Pzvw2$sUh<+#0gL`*XfeF&!0vBnmmbn8?=^fJa*g`Y#`BP`vcS!tDt+(1_O`vx zIBU8a2iosh7`={I5t8(N?A;Rs0Y;{?J-AMw*J(t#CNb09e5;SYvk6omN(fuu&V=L? zr5=0A6+a$Yd%JGi<`tB-d^XJXY9=P^E6umRl?L!cxpn^?8YBd`YAV`)+)JEI#t6N7 z#QB5DNc|oTA_752FXeh$8hqjlq&v^|B2ILPNHqrTEAfxDdOpwjt$zL_a2HQ!5`k+# z5tTi)hf4$Z{+~wuc?u90L}9MkU<_45Sf=NiVX}bW$Pn6>{!2ZS++ZU4kZ90!-+KrY zW^h642WV1{#9r{?toQi@8=7qKW+)WZ;UWgdV!ZQF$a7Fa#7H@3-$hJHtd?+j^L|o* zf)6ud0~3*@=XQNk$?x4$B=F$9X9DBn;nlExZ*SuW`-zA?Q4OBZR#%)7Lu(1Ce4)6C ztYkaa8iJ3AeW-Jr-~7n>z|(Hz*D8_Mcfxum^$?59eA%`B-|tB#9h{7VQh3K>U$Ty^Kof!^Y`l5-~vbN z&S1yCtkt*T`(v??Mqt2GW_sPwDPeur)$kFi!iCR%{L$~!*@}sU&coWCqY(jDA;tUs z7Nh(?Cp}Whq?{-yH!Mbp5uEM`f|v+@L^a3HXORU&C*n}TnM;79HMvmKjRaf@=H*WJ zVLmGV*UWswjxX(^eMSbE14okKUia!V{yi_iFRa{{U&SG@+gZZM$7YBiO8%ij2krHou427J+@cp05?d> zX%t97fYG!W>bqUTd(-7tgJD~;w|VIK{>^iFjpkz)m4mx(tEZc;!hPamRw}vw^74H; z*U*tZzZ>9mrbrkv(22?DJ=`JWrGYF#Esk>MsQNWt{#Bx^Czw{N;1Z1xLRU2 z2sL)w@0;22ZdR2deZuck7raEe6vQjZ8^J`p)1BwdCc8u(eyRijW}=XSKcm$R{mn3XrYHE>X8Q4 zh40nc5!1pCFE^}2;rGpqU%pOxr;zNw{DEJ{()t|uCr#*`Y14yW`#5tR=FKt0Ck-G` zjhEvoDj(r!|KurzW3cgU!p5u3H9(F?ipl=z<=uY1yRaJykF9Q0VO<3+=S1Tg&_}>v z%!~b*(-TF(3kO!5$6T(hZL+bmH{aa%ew<7zqW!tr?kWyA91e-7ugD#kZ$3(v_C!df z;qo%`7mXW#nM@CR;SC8{Idq4P_=1Um@9X7!{6Kuw^VTZTg;_UtkI3EHSm~-t--s_I zYpFM34v7*GL{~I#4g_OMtQN=AX%4Xcz=G-y=$&PVM@gKJ5t)T7YD4N`zL|b?@-@(I zC<2sQ|Gq8UzBY5BL`!^tACWgLM{^xB^AAD$qRWV)jFpI%j;#~jU!oNFD&5=jy3J8` z*meJO2fvGtk5`=)c&Sv^`wS{%@%7jH%^}irpV`~>o>J8^($i+{zw=pdtiH+ZEm_ATN3 z%I~&z_kQId7#yA76(c5~iBc@`e*tSil)r$3quyz!f8mOiwT0fH%@17kn|0^@xMazkssw4klMI|`7eU~YX?ttRmD>nEQ%I}gOeW4Lx<{XVQaWdRMTg)H=vZUbO~ zA=@*^Ak{~OqedUa#0Q7)$5wX~Cv!qoQo-ypzptR_t>@KmgKOWF(dgv)1+PDJ=c--! ztGE5+SnY66xpUu}O;aweRC||O>w)6F_g+(P9$NjnHP zL+7-MjI#)Ly!y|3SG{-KwLiLa=`?jO<PxIGX!Wy&zyFdZyp6iH})u>J}-;}bAW ze5?yQZsni_*+61^nt==k(W(orjmYhey?Fl}|J;Qxzxl?K>araUL5U9U+1DfHSLJx~ zTHbk1o5{Dz^VrawTFrUECT0W)Ie8{JJISdx$ziKetanmiOo;H2KeMn`SFCai6J zN8z%ir|#Tz*98|m84*g(|Jt>)(7G%B^6+LfA#m+ptV+Go(~j+U`Aa{#Qu72lE6zFf zl-9fdc;N+q9fM_;ed`-_^;51ncik_p|F#1PF{$D^U#;8p+rNL|mTzh5>fnxN{&C-` zx1$}=(P!nrP}YiH-}I>;pSkY;uKK(SqIl79zgbzQbpV36oQNp8mO8lk=9?egwLj`{ zDlx}|%g>v>`GqZ9=cz=(Y)()T<=6!#9tYYEd4QA=a)Rk_=OiJtIW*XC3;}KAN_7|>NFnjKI z&e(X@9oIa9ae(OpJ1cTv7B$3eBKs+%VR=GM9 zu@VbO*dE|}P-NU-CJS`-UbS@KJEucVW>$bg} zQeoA!DHF3}-A$c?;czS=%UVRetE@~M&7VBQ33RJpkO$hEb~X1ni)*JhRQV)T)P#3# z+c`28%POp&S?}-J+g>nrvI8kN9_;Gs7#x$4oKTP=J4$9wsq~6tTQ=Kw6wyY9Z{o_p`rVgr(u#uNeHI8E3pP92g8 zlLBZ{29XP}rKPibAh=}V4C9M+>$m3T`fIDJkllZ%Yxqze91JShQB>$ljP4)*6KnHaP(+~}TXzkFV*ctGv#PRsTQ~pu z&MjAbeRj`4B(Gw^q}mAriuUx3xxL;TzZ=I!x&mTpL6!hfc(^;^E6;U7Jl=p(kS+$5 zjW1O>ew(`C28Na_nspRV*Er9j_$X7E_z)~ENCF8t6_bvun5-*@2_Tl#Pbz2bRMn~p z0LS(HssJn&PMCK5gz5UtiWX-BJyT~bG9-X1*-EEQQze1cw98*nH?zWesiMohyjhEm z)!!>?&+5ukPC>rj4Fp~J4O5F7$bQ(ns~^|{449Cr6qX^hBTo=ofi-qG6BFeOB;~%t zRDl6FX+ANuOIpLTFBTT%tACOehnH z$DciY-a_L(SxF!zJ{-xPJbk{pFp36csph7mqM{IK7gp!0x(v%Y9-3WLt@0^^0ZbiI zb6~Xy^D!MZbQB*=;cFlJNxl5c+w>Nc;HggG?@<`X8rDM0Sc#!oba7^9lh4>dM{RP+ zXq=ZnNFmRv_s1!Ao>T&Y{fJYgI3zh3(jhL%Gl^{;4q#7=`Y?bSQWW!fgc5Q44GA-^ z8YrrKL^QhB-*IvCTs;MoTiW_TLO=x=lJN(r8okqOow}B zy#XB{luCL4kmR>cvAHqim+!zk8>aY%W+A0pajO6Ym^f+DuYdJR9kUTqiK3K}5L*xz z9v}aLAc_1yQl`0(G#_S~!2#(L9!VqgX`O9X8q#TGfAvJM&RG=m2$LcxdTbKzjnzByqkZcTdRa5}9w zrgTfdB;vFc$Y5i+aC)aHOctbk!tp`GU~(Na%%{n7jsZ3YDovZZc`2mqD^7JkPGb5W zntWgqFoVHS6~O(mXV&91yOzNlQ_veoa*;8~Lnxx?*H8KT) z%I5pSBwmnGBZH~51Dg-qQkEf1o!P-s5ntiLLRJqTjt;0Re%R$s0k@35S(NDpYwe%J+o~VeKk5X3n%CLd)sp}NWlp>#NnG;R*{ zp|8^7g65X}Z@t&3v2b7~Fl>>fRl}LyW0jp}sMpxC2x9@V;Gy)zs|F`eOhl8y)gupb>`ysxMrr=+M*Q554bha`3P4fYQX z>uKp^)R0rgAydbNaZ6amy`i1*=~Q#*F6D-FT5QX~ruMUk2tb}J8Ac2F0#le-Yp%+$ zdQ)M3Zdpm8EXzi5A*7(JEYFF@&hvQO|D!fxQs~)-xOVMr5k&Fq6-(2YPiabb+J8Mj zpyoK-hAE9SdssO!ctg%L-&1JQJ$QL;!4FT z&dCSKNNk!$QXVpM;01!rz_DV)=;%f!(t>62`rZ3J8OEwv`Mufb7ii>yB>Jt>B;pIR3bd^Za#`_Lik}R(DrTF$>Gd4z$j6yl4(=(4#d%kuF2|7Z2N$1#C9{=efoX+S6gpS+7_%rYx}ERh30ig zR>MpoB`|ZYAXO~6Lzu9w5`N0=0o#Nm@9E_69$@PywZ^KW(Dop6AckpX<5XY))}qOk z>67b@nLoq0AL-HN2kp#%qa6IWYRs6^M4o%$)wO$@J3x}%#gZ9IZ|wd{M?&^RO}dS} zll|tv6m@`YXJZ!Wx^!R_G@0Yr8J>L-(z3af{K)nv%jN_ewWsGn2j{J950hW_q55y} z4thclc~&L4-oByiY@bSsKtHVsn~#3NB;m(!Qm;{UTb)5cusjnZfuL&42!hC;QkQNw zr?yXQER&T{7Il$B@*p4i$sP>KV0|{6z}}OT+b%t!+6Ci*_jh{gO>;8?FL4})k%6|I zZ4>y66ou@e8JLksHlDyrs$_)hU~)3O6PUx&V467*RRt;>j&fTI;c?RWf0HCUGG|fX z!+bromAgv{l;JsRS^+|bV_ON!8I5hV0vW~64CzWyL;;#-2D3$=&Bm=#^7`pQ*kCdw zpqVeBWqJ9H8zA#`%v`kp%n`z=r;gVDC1sRTh;c|l)vP1McyN-G!naO~AZtPs7nq{_ z=3pT^2}Hew{26Z+X{0-488fAR4aJPkK+3}&NmL$Rjhi&9Wi(pTa#Tq|;SdOSJi;)g zET!juVEzmPcJ0&2;gp&igZ1;Hg{cKOyH`@3xWFCGX4^mp`!Mz~CX&S@PP#cDB@Aej zqTJ}L=x&&j)0ySsp~WnW9Za`7%L@$7#g7Fu58`B`&c+w0(}I13;c+l|0;yn4x`*X% zAf**RsX94f%D4+NamUs!VYt{Q4jmn@I!gD-A*-YIIpRK2|1!tRRbyK}G=bJ6gJ!e@ zfpBc3WtQVi!WEdG81Uy*vq6}=#IrF7Xfs!mH7lx~F*g{rX&rsTDS^zCa$_;hi{{iS zm;^6bqAD35GJZAAF1Eg54@RVqKnw37hf3LlVA+#(=wEH!wrvlX26VwG&i&=?)ZNxt z!&$VbM&k@afgXTr8!XF}h*=0eaT@7|Ai^UxBug@Is{t9|W8LF&I&yOITn>k^fyP3S zU??ohieR&~Ag#P<(@{CkFSqs6S(St+Q-XxXw!rq>9~yqCHLS>bSqIg|6#zB2AXCvc z-8SNESDva_shEtoSQ-S8ipI9p1v(XFrVpP2KRli5q7>1zNyxyM#t>2fQ8vC~gFutv zAAYiM@;vK4KkRn9a{NB0WGp%y3x!qlPdlB4JDFJR6=LIG1A$;sVZJE9BS(QKI5HfJ zDp`32SuXLTQ%H{pFmni(08j|yD6dQkbXyoCIH96s`sBK4Q)``))3~jxx38(CbK{oX zszomHClfJH9%c+YgSh!`Y=A`4p)LGgx9reFA|WfJ4up0#HK+?$=UgNIbK&h zG&VREVG_DAO%jXf|iFhQYxLr=c(sQSs$zC63qzt39 zG)g{#bsT{Gos^s*O zm*nT>Xraki){4;rTwYSF{_|)1UVCeOERkTb4VL=sDs*~JfQ5jk67>`yF);C}+S>dq zCxHg=*vL?G*N~(#1B(tpA|9W$@R&uDiu-rG|JWP*aFQ~Q8M}*q1DSf>*q+M|+1`>k zIlu+Jsw$iu%PA-+DatL%$@h7co}R(pj+U;Fsx#P9+xH)zT+8@}|}o=J;|;iyVQm(cb-C-F?GBnJuyllfpZ3swhy__hVDhp&b*Z zgCYJZRS?%zSE!4g>(54JHVBq6sw^u}|7B-;)kRmiOgeZYG!~ZiI$fg3Nv+<_-TMR5 zqPf#P><-%Ywq8cbvf+&XS1|JV-1Fv4 zQ7KTy+Q~jD1@7F}QdUyn&&pC4YxeX>t?gYq_OuF+VwHxBV8sO>9x&_IR8B?9GijH8 z?5}zSq`6C$%q+126bIV2|Ms!By)H2ll?_Wqayr$nc)eb?Bzc{QP$Z(Iv5O8>+NcsK z5{>D_A+T!b>W9m#`^DjLib^7`@@n-qw^NA6;wsz5dX+LLN*Ziq1)whiYP1=EIID@IraIpd;1$-UG;Wg$%*X(e=Iq>Tzq%yqzgaUHw(V?I=bdm_j8nPS%yd-<59;aW@e=k|>ZJhjcpL+F zd~D#YP0i|EfuRA2U0MFhrN@;>+EZw_vt?6LZ&cBW{yB52RxHm~H^oSA=Y}o&^D0Xh zO{qoU2m+4mEY;3YJRX-k+_|Z#zo=&V)H2`bU~gmd;Pe?Y%CcPsrWy(KH16J?S3PM` zdCt&a*q`Thh#FHF9qxU5b7yH`#fdX31tqw9Ut33C2uM&-RW%%2ziFqVq;6J)zhv_K ziOnr41*#40?d%i^r%bG>&#&m1BotkA+6;p(<6&9j335+US5cyCS5qWs+U%LN{(OH? z5!&64v1lVU2E8QqB(smK7!1hbx11k>w{;;kq-8}eD$J`WD@HnB7NGFKrX7vj_I~zL zr;AWfG{%KyO`o`ZSF<26J|Rra8z=)&Z(JVg?^isz%2?Ohw+4@0I!lmMY3hh4)Lj$U zw(*Uu8lM4-vb*O#IYiqZ7OY5A&d*0mNed3B`E+wvV0;dv*g<~Wo z;Apc6?IM){0JO&k5xou&uSftYOBnQ~e_yJ2$qCO`X4BY7V(csgDdk@2ja@ zGP_Dn0Lej&oU!hfU0wc$SxXjHfND;_|*UU4@R3cP4i95eY zy+6>~{nF}9HOs3e=4Pu_p+8%t{!sIvTv4DMZUY4n3%9-cTClFzopAakO_htQLRJu9 zs(=K(G$dq?3gyECjA}eApk;U7Le^EQUS5yO6Ff1vDE2ys1{&R zQJz7fsW*|LK3Pz=L} zd@#DFF4gwkP5pzzZnulGwq*rFdr(EHHD&Do$*pb{G1(bAt+URjx6Fk?biMA8(;SfG2&`qm}Oj?Kx+pFXwvrQOkxDjrpH zzkB~X8@J^&%vsn_7S&;%NFt)rtg41aM~BwFv?e~YR%JbQwjt;jq_sH5N ziz`Yz>~!j(l`a=*-1tGT-Q}*HQ<9%!%&DS?2|4e$>-f(sBintr8T@@n^mB-j?MjE^@n_#-iIaVH;kHARcOdf8FK| zS3#K%I8?$xO%JMZ8&yxiOopFtJH*MO!x3M#% z^;2+~#cE>x?{8fH#s_->!6;SYio9;UaITQ$t5~vdaz$xzeNA~mw$@5Q))YQgn(8A4{mRGN(Mhyuv8@Ys@3IlzrAAj#+q# z`ph|xKZSFXieE6t!GUC_zi|x@k6n7v897-#qcu?!F(yveRPcm4$j%+xdr`&7CoXBI z@I_^nQL96XB@k3+(_NG!2r*|t-QvYdmM)nyWnzKZ6PGh58};?KX_!yAyRU7;>-L#5 zgFkXl<9|c(Um8`xh#dir1w%uDKwgetRX+;yaxOma)IUG+R8-p**kDmDZJnk^$PA1E zZ2V!$h+z(6ek291jj{-PzPLh6{5%!@I}I zibXBU)~QM-n4jh5fLkS@9U?6{R3@Uerl?{ro~xaC3b4abFlpMfnhHF&f6dz6p6cV4 zO!TO-0qLDmVzE#HOG0Af#;!RlYJ7QRUI+qh{rk7~xI7NMrD*QN87c)%$aM_1Z2ZT| z+qwcuwRT_`$nrRg@|1*r(i?^f6eXu<89C}~I7Oe|(fRlj{m@@@%(7)C*7&`-#Td0& z)WRIe$Orw|0;ZuYqKJ(G#*8;5dFKg&Fgh9>35@0BWEnbVU^IBaIm;HznTE6lSB*lY zwY5|8y;@EhZsR2oR_%b|+M4)VyH`EdCPyM3k3RuD#RX-tP3!*D-MwPP(&HCQetpxL z$F{2zkf=Q7+~ewWX)k5f%~;&=`Wt`wTbmdSW|hyFKCwyzW;HJk`tORENheee*(++B zM=Hc5Q5qQ;+uzZfpOa$?Q-e{gN76C+|_E)6&H7Ak3>z)HLcxYI~6u{ z*fAUDp$eS7{y~)m$chx|-XEWv@65`psjkVFofQS{H+StbQciQKXMXwO+JZb0pm;Pi zs`<)w1|#SIsnM>le8#1pt}iI`nCG$S2ALw|O_@5~SKtDcxuqeZ#(81E{hRtCH6rGY?55nZ`?`g@fqNEMQnEU|1#Cgf^|a~LM;!Dr&&%x_e#G&C~W z(bb!qlWi>e1?QZUA2HthwiYswS8a3JUVv#f$$xd+!0?)^**F-+SM}20?(E6eSKx)RrvCTHY&(SM0>G zvz^r@ZS(6dP5Wzq9sjgR^Gn){Hlx{1;y6hY$0Nflp7PMLWbM5risAxDfS7OI|9$Ua zya#|F7A29!4L=SE0SD*ach5cJp7T9-xAnz%39+2r;YgnV_QrWP-r)^}V=`b}b#A+Q z{R=kUc{k)N6uA6j8WXr7%ub_8M^oZFzkS!iQ|B&FkW#y)Va4UJGiqQ){mkoI_nhq? zL1lX^y2eVnjX_mZ46ZAMwW|NLyQ)>T!bCx@qvyb0-5L zrYw%uCL)*G=A)9@M$tNR5Kd$9*b4{z>cJll44gZJ8BWt_3MNJ>&FBoXHb;ZE&Rgda zqroR%R--l!m&?Y$^E>yQJ$RmBX^_ee*N-Dvz!tl+rM0=e$>U&Auz!F5$s;t&8m;=2 z*cg{L6YI+3;g~LLC(B>SJTLMzV=;c~&i!XET%ktQ}#g zXJ3E`elN?Bv%=AcIruuxhqOOV=@x!LrQHe*M(iRZAB3&i&!TPXxw7Y2RIc zII^s`UG@dtolBQ=>o*f*gP%Hk;mGl`r_K&cT3(PZ8EVpiXWj(V$v=yT=ppsvvaF=_ zE>^Is&eiCvrxSsJu}CD2Rb{7z9h+O5St)wvVo-JX$Vb&zRagp~>Mb*OP74wO{eDSL zzm{a#;;^-~G{u6!q0yjfvTE}rpHwqnWK3K-yUn`k_B&QIal>bhJo1~@B3AXN)*~Y{ zO=aDvlXaF9;BwkK+8W2k#)pQ-h(gvc>a?loy_VEVNzTHyv^86iJU$*8@rU(XbYQN8 zDp07IRhE^vS~|DFFt>!7Ow#3Y^!3hNwz%8nP%jJ-L?=(5KYHTa>2nv8S?MX|8Hq&i zdi!<6UIl7p{qJtvbX8kRqn<>Xoe+Pe4y`rX!A)zIRNfk`^1uw#wm34XAhJoqk=V|? zM|SNyl5^{j+f*87!87N{iZ%r9(nl$PrVILXwT97G%;aQaCEb81)j3xE|8Qu6{G{rj z)c0%fOUi|F{Yi4#i7v^KA!$ox;OzN}dJrS6tNCdB)X6jYACj6ass2WO(Rr|RuKSBS zdo*it;?(I=V-#&krFv_@L&*ftqM>H`d@W2PF2 z+;7QAI?I!3nW=P4+i$9gXOc8;Z!aBVYv^Db;g=v4l zd`0N_N_CCdp~kpqb`ee5@&txd=&Z{tvvHLjMmJ`Pz#LeO0>)Ubn+o!kGG(cbbasI; zwkq`R`_X^z)-(p~uyMI3NP+XtXzd%n*9bY2Dg*<$9h3}HPDfMVWSM{<%M8iay((`x zVs@k_Q;==U1TZ+0vq1_=FY2xK8Bm}Xi~;f1(8b*Df>_?9 zyh%dSrXoOKr~}ShKAH{l1F*Rw9%dDVrgJc|c>p-;zs7Sgb+qL!QQkvsHk+epIxh*6 zODsuRV`i8Scip@{jc5@@JPVk=Iz9`SEWNjqk+odYC?otZD}tI`{g|6|Wjr^sEImVz z7ET6HW5njj#sM&VODcUD8ud*2paB492>_S^a{y!=VL9qC$M-hS)T@SJc1}`u21Q=) zAbm0d3_z|ug+Z=?Y7GDA8S~1exW}7jW3<953RtB|-@e|tukY9!iN>-3s?$jq5M_h* z={pr6n7u!eH@P6a$+Hm9a_--8x;jX|$Rm%Ikj;zH1hPTegr&*Yw^MF=Qb+G%}tL zmD*Cwh*3?*!z{#?seD4Y|Ll>SI}Y-Qa=ANJtzM?XnUyy}fq_>yZ?|`JFI};sLB(NF z4(#4_=KO`a`5V?PXlBaz;E~Ym!OHwZL=krC+{& z{v-eRcUO0o2`=p)I=5rTK0s3vA4e^V*01RES<5$w6e)c2%`N-Rj4R02+PiRZ--3Ee zsrsZQgwx;?CN3WS?%)3D4|j~r^Cos3Xn*MGpMUV?p0dAd;K=Krec$`zYajT+*S`PY zT00O!>X9dFec9X^G~dEuW?+>PxXWFDf}cC_#5g$ASb{xg9K7r~swex$G; zx^0P*gUa}shko;iu{hJS^5(m*?JaWZOE*36-EVYlfB84BhFc!I|Bk--E6|p~r;m>x z-u&31D9^eZZ@>F?pS3jkwlPd&`_dI3{^Yw>x3k{19%tx<-@o3vbUA-y`#!$sy?3wV zie5x4MGn0B`_r!V|M1VB>yi(hjn~hw1OAgcUU+Tiz&LNOTXf(1Zu3snQfcOD!eYc{ z+PX5C8`kCyiwm+I;pm|e@s2?3cd$z5nlr@vdvvuJ(z~eea)t5xMi`Tb8+s zohlG%j#7rsp52!K*KgZ#@YLBLL(>={Ie7lxzy8G^Jbrl1lE$BX|91ob^ml)H|Mg5! zRN0P&SAAgN@_6u#-N$;~e*Xi#9N7AwAN<^Z`rFp6X|OXd|L6-p7`o+#+m_cCvlwoyz zUBz@7Vp`{~{J{L>AhCPP8;!TU=l;buu;-wa1?Rr?(4&ieiETSZ5015b<u$VF<3T*~qwjt_W^sr87v%Y$xcAN#94h{3rZ}4;7CJQUAGzv*58SLBs|SxC z-0|iSi;MfsPkz78&wlMQH>kjAZ&giLMDFPHEI`+qcl_(Wymw&h_uv2Ccm2(Gp6!nT z3up6rXhf;yzZMJU;Yv9EBw28@&AZ{Ijs6Q4-@I^P=c$QB*WW(3vr$pd;GQQQdFG7| ze(pcM{f#fZ_o~P*fBF;hU8Rg*f|szEhy-Z{;FQ;!VAB6_!nYHOvWW3 z^3kA%Ijksx=)CJgfBm2T`OOcnalQKFbD>hV4sCIXXaDe>&)jqO?f>?R&7#D5eB7JE zw)gzqSN`iC@3d2}6jHV|cYfwmH+TQf-+$=7cYplXPrfJs(7Wc=d*5->s%7)%Hu2BB zcsMTN+3OQlCpucm*H6j;qBw*1v8)eqMwWglxUjidZWSQqb5i za%!7LqMgleyZ4@}UB|xqSD$+7LfxGY+~3~lk`){py5MqfH{871+uF14nmG{h!Fc&o z(GU%pdWQufi!!d_X%=ng!N%6Un;QFn_4P0R_B4CL`#*m7^4^!f_s#FWxMS`0y*?)l zUhH?;Ei?xNP=C)~{{2Vpzg8~$^aT>HG~RyQipHjeOPg6c2qa3Kt}O9P!@_Is`QQgX ze8Z}_2uXY*vG}$JK63l@OXsyvr5(%A;p_g#Z~pM7KmUH8<;<5KdS?HzH-7W8fB)$p z4?}O~LZ`cqsO_l;&Rk7ctzHzzcB}aGPn&oq7MUZ5#?B2#jYrD?v-^(<(`QE>+AxNTo;qCwS7l;1rYhU`< z;|!)2{_~eU*Hl`N0`dtyD)5T7Q|90Lfu#@ceCVJ5>hb2~eTzK%{YnXH z+}YIKO95u-^2QEdFnU#cQxhTWTk)QM|IFyWeDmKww%H=mp6fpJ`D@pgz@)$n5k4+! z&x*o{STrJNfU`n88jZ=NK0;X(;t4(`3TeY79*al$xPr^sh2f!--~W$?UuxtMZ1YDy z_5PbTHTM60`Oe3_df%QKR!ZB*q4Y2jdCgaMd?9dP+3Gm5Jb3M5KIPj|Qx+ghu^i6BEed@z|A^ zPy&;xNF*Y|BO$xnQ}4C`ERF>N5z*S*;7Z;QO7V+>qmcwpS?r$r#(I14r-c#@j)g_! zt*_@OAcO;fu;gh_k>MkO;fTaFGlj;lM?|%Vlry)>YEqlcuRN zXD<$qj^_&DjB}2PIW(qG6V{@mSN=ciuR8MlWkKWYV>+qTYR!y z5y8+zG@cMyilC0Ge;!9soNYNCW zwiTv}Fkx%jZ1##^S;h)P5Mc!am?>4w(1E228`<4zFcbqs%ut*^{7F&N>GOlZiFmrG zSM@$2H11AVoHRi5E5)!YD|qpO4whlcJ)demVQmx+8PpXk5^-5u?%$;$d!W?@|}jG5Y)nL>+Te4Kyg)xn<+ zf)xv#pTF5B2;~Q0AcbQ6fiFKdagxVQ73dOu;M#^e7TEJ07J9r#(ONBlnF4Q7?723b zrcNy|Guzar5{v!%#lfFlz-|sTuqdBTSPCR5*&h%!xape~yWY3l!YFw2p(sGJDxbNS z?1E`T;I54svPqJon0L#u#3;+$u?XxsBMDefmeD?oGvZe~L0OcYb6p<+Ona9rG3@{C z5uRi9beY)`4!z8fhqX?}P+dgrIFv0B;0(bFN32#WeS_2W3*av?*n zC^MB$fVM7Yd^q&u&Cw`Aw=8wYqJ$BpO5!ZBHZN;EVa*(!bpi{p3^^AY;uUF zbB3kRLo|vFgdf@#d|?PL_EAnz*l~D5LR1P@6qjVwU9kp#MdoO&n6Ne`*;h)OS43IA zX2WIsEQp4qXTzHZMIL~|{fWINqm5qf|8_YYurdXEW%=CI@d;}tWo?*t17~yn&0Thj zjS?~)xyum~MN}I4y#LavCXXu^$QW}BvJ*!W&z=BjwE{D>F{@=NWkDAI8H#2XwyUxt zYNlz@@4(siZ84;5b=;zQQyn8wu*uKPlEc52F;g2;Z;>)ANi0aF{RO8w@+Zql#tJUx zwtseeMYW8Xx4I^TrE9_*2~kNHayav7!jkCErfisMRo2(WY}SO;NC7i_yb1(ol@MM| zb5u)ws0DSd@KYM&Sd5Qa;+3IMNQne*vs!=}Ur}O-L?oK1q*R}#>3D*N@U4Q!IekK? zmYUQ|*(-e1dt4{ZoIiDz!o)63J}jKVV$utR^i&vVFs`8a7xe>@EVnfI$nRc{`}Dbs z=lX{X2vO|BDiy-)!|dXkq+-B7};ysivZg6@jf^f!Mmyz<4aqXU_uU zD^Q*66-^!aymiEY92=hqM`MLPlK1uOKUIppdbizX9S==Rgkw`Yn!-$`2-erRoDN&f z9IY^OEn|-E*ma=3-i<1-@+pey9~fSC z>z&s@XdXO#DxMIW4r@gO5gZK!J6fAoES;ZTp0}1?d6mR>A&mVlvYWg{>d@wS%X+N z#rXim>L1E&43hW7tba=WqPt~CJ#-j~zIxRn&canP<-G@vD;hKff^hkg`SqSkTUH-B zdRj>$m!cJmdm0RdFMs0C)KY?|`CK!m64&$BEm%X0cUiWfwd6)VSMCSP<66w)1K@=rPsw6t95|pIW!^ONv$V-(#9-DhomzD`b6;Ur}6^p7n@=w7>^~0$`Xr zj|C`L#@V{70@aVPAn-C`EeuTEAmijOIBV(Bu3+kzhiUDHAiJ`Y);ACl$B}X2VjR%O z^w~+46i*h3(JWG#{10-=Y;C=m9}>s*NqihYsyv$lOG3KQiw5jTR+h@Kz)Raw45LH> zF&QAc$3`iNjI%Uhsb@B1XeBZhmm%e>=M)j+yaqrmFms8=={>{WKFSlvUXpNogXR4j zeDge%@whF2wlp3Yiu~xwv3Q&HL+iZrJ+x61W>wH&n32qs_RA<3YqRUmZmL5N>z9Ks zygB@n{V^wp(wC#@dh@(Nm$WoOJrtBY1r&&EmW6-oXy}6s3veiQ>bnysUq-CGj02cL z!sAblZiy<27CjOq+}&#Z#Ep$^U^JRbT2F}N=+B=DzA+(-3ii60_iboc)WGDJ*(E4y z7Ds>mRB-FKDt%rT_vdeKY@lY9Pgu-)T#21}trjK1<8BX{%P9K>6FbhwblVhH7DgaM zgs}9+&hag=NufeF%&UGIU349$%r$|#5~e> z7#V7*uxGY&sJ@4x8vexDwg#~r-~)1aK;b7qnfE%XJ?&(agecRJ!pG&&s6zC=olZ+0 zEPVk%bRf26OmsS#J36SNL&DRC!Y~OBpPY6{nvkMl|G~&>!(vcW8WBD<693uOAjQ1p zTDa3$Qaf!h_$55@#-92wcyYa(I(o*x`&B_kP`{Q-`$pv*3zQ zu}LPOz9z|Aid5K!FkrJ168QW;=nKz}aTJJ)vV(=WGd0FP)3H&bZZt|bK*-v0HH4Bd z_}hsizXBACl^CMSyb@Go`i5KEt|#n>@{X-TKj?=m=hs~Hnfb?}k8BebG_fr}Iw)&Sl6sj1 z6;pu0eD{#rvN*kWHD#wH-BdWIz3zt}sS{)IN4NN&I3DNNjFK@rHCdvVHbixNPF;WM zbD=mJc_>WNeZH3afb5SP{Yv=UF7-u2Xhv4&E)_X-GL>j7^84dbN2~3cZjR?AhJi}v zw1tXXY-==GhTbk~-`>O@cLbi4L9>UxYpGQdB;v7RV)N?vgb>SmXS5>j2_ezUtlEF# z+)&JA7X|X*WjQFxbt*k&Hpyb)K1--@%nes5FjW!}L=Ia-6H~9D0gVNPLjwt#hDPtd z9JN5S!M5Nj3w3pug_CfMmjhuzPDe-c#Sqg$(`0xCn-=uUx$w3#;a84^jtonT&AP6~ z(I{gH8zUb112E~;Hr@&vzL9)UOb+}uc5xS=e4J-BL-7?o0<4@k6dmBu;yG3)1eFAv zi8&e^;r%jhspt9{X(%Y?{9;xnVo9vZ66l<3>1}3SI}rT-8}R^EZCfi!r6z$I?&ziF zBr_(0f~C{_Pj9b(a5+0VE*}^YHMuN5I;u9-!s109Hqr8P2g6$-h zp7Q5XPM~g%=FfK_k`j*perMp(H-k?dP!rRMYlyVBPZm!$24nCf*Y27Yrhyz42LDI! zZ&k@!?1$96&e|fe2!+mTW#BL{T(F0=U5YJ4$qPVQ3Ovc>NypRWzjT5^@ ze>{vAyQuzv$kovcTR07lnKF6*B^u8R!`*wGTOig~RDR;MvDd83iaB-%m5q&()lwnj zJsHC-YI73&f!jKkyHL{1!1CGQ*eC*&<|Jh!JD4LeX@@YKYnuA68XZjxy}@>_wJrO# zmKPgO1a^yY4zeJvVsQ$YsYnap=)Q^lGPtST-e>^{0j9t#Okz{yViJCR4Yd2T@Z2u{ zQ4aS5xS@ez)XY7YkrP}BUIv7;6#3cKa7@C71_V#L_3q_P)oY<<6Tk(W(mE~Xvc7DW zv`mYlqt~^H6BCj$r;fX=#d^a6;w~r*moY~%F8%TnqPiv*g`gZ2*DjKLEYsJ(N}@us zIPJaRWV^m(FjH5ChT7KjRi3)fUfn+)>990d5YNkJE`(n@8p0ff%r}H-?U$X9o#QJe z4@@k^kAEF0A7)+c5X8mtR}&Xs1GFvQnOfxh6^XUIdpWv(v0co-69QaTvj^hswRF4g zOVIm5QdEI&Tg}|k=THO%F_ht0DDjD7N_Nn(I9RpJdh@I%tsAgaDQPRAfm-T!0+4ZhhlAvfp_{!k9|Z#Oc`* z+DSP~Z>zK%rm{U%A>8BNd=#}GNh8)K2Oef&C$dcQ+cilihY3@0ftoVg*HO3l%VG@&uuvXtm6d9<>Y zxRwlbBufZVyVtOh$~zl~?lM_oslw6}YM6eFkh##-OPY+hn&b~Fhp-F+VTm|{^)|}r z>0y}16~rJD1%)}+7#R%-8N|>)3}A>Tt`5MIHa62Ne(pT4WSzd+jE#j^rP%fu^E^)F zE-1i+qMx$d3lhxnhcPc|NpNx|f}aIsT84g|S%H~L1bJ8<0vF8k*HVIjaTNi%RF}qZ zbCA-4`WNYyN+~%sUKQ%utxV=zz^020v-_7Z4REd%inHaAkS10#&YS|74D(c|wpv(b zIOW8ao^56*kkQlJGbsmCKkM9oh4~qr@iGWu#ThfDxF6F_1R65Is(Vtw%*~#msve~N zr%QeMY)%R(&KQR{C4A|6iyW8Fn23`+?~S=~S(1rOrH0Uu=UKg+%Bos;8P5Y3a~y-W z(73D^Gv?N4kY$-lih6;5f0!c3GU}+xPD0w{!ldSV%J{-LDgL<$-_T%;=9VC3l4Fu7 zG6wrLJ5riIkfT##8M?B$LyAIavJ69$QQ1mDns{`niv?PJ&NM|;N|5KJSs;Q}5Zam~ z%P+jL3nEzHOol1{TgO{59+zqb0EEF%xUHp8-uE6hZg3VuCDd`%#e-Sc(JPaY{yIQU-9&tsgLFk!1$C{MvOZ8+@LZx4fB%3p&tK%5Kj)<@}(Zsz!w{8jG)5wqWj@)@{2E z1ja)&MW;WN*90|^HKIT!znvK2aenEd?%uABU3(7?js(3xC4eN!8`ds!I_$< zWg{LCYP4Mf4UkODs`3N34eun48 zw@CbS8Z}{7+M1%^V(v_e3^WmHidut&t9w{!WGiG680xK5CaVIIleX{ZUT?(qq*MhG4piaym@Mw=U#iYExDP zcXS4L&#}bo>h`v%;f?q&x$SG94*{) z2v7{gAcaYF!(&;f#CTbd7#5+jp9u!EN>*mlz9<4L0~sLG=53wIAe+M3BeWQbFsa#I z6e3JAf(SGwX=IROTnd-Txy$;!1I;3ql5v8ElEktU1#>R$3&OgnOwveONyV7k$r82| zg~~~mfK!3>s%a9mHfyM8xcPwmqH?RQssfEs)Z(^Hh9FQ0s)18PRuV&XB}E2aDntCz z8d)W4!mRt5s&r*4u#AK}%hAG6_=#iTAjjRm+-;$yeY=92hXfv@rCp9&7rT&#L}5U! zF6v=(938z7`rXNh$Tmp%pE-y{OKDiKt>A%UhPka5Z%AR3Cx{aieWN*A@o061J4i3?k0HO z634{I#LxE#eI7ayk#}4aY&QC)Zd*#z;KFtaVUiySKe09N+w%gfv)r}B1ud%P95^`k zv%OL5VSDB|d+R9k6DhXQ+Kvr7{eK9$rv;Y-w? za55UisH39MU+)Y&aY3|jsDV|Vz?qY=QJQ+k3fD?|{PQoy_nnPh+iA^Ftp&fBkPU?& z+7WneNU>XxmxBo7;q!s7?F#!b^x2S;F2!LZ)MC*`caHx0ylCa92CHf%dgeIaIZ7{H z<^mJpFFhAMJDk(6dk_|^5-r;|GP_}avVUqZQa~69(=S9ogsAH$bzOY5;pO8|i z;TiC}NvXwSkG?tji?b5Pp%yEmwa+Nw&=2>-CoHIqQ*Zc-S1c;QMEuF!qYs@Fh`rrp zK}03$N2B;)=;eO7 zr^orRtLvIMDAANCDGZHf4FY#z@kyo`6`={MvlYj;bJ8b{j2cFpzyP#lM zQQ|ysFmPj!qeTJ>K_aGB#4D5nw7aL|`K{;Kz1EV;VrB)y2Gb zjf-XFpKj;libhTyGB%`6>M!L`Qy>L<+Z|t+kDuQcJ0oLJmYrVvJsT`e7kyxWY)s^J zBDF3R>(LZwq&De|nL2m0q|>=^8UEeQ*qAnVi_OwQO}xBMK0K(RGp=jnWKo7{eoTg| z6x%sWBG}dD`20M)`EcwAk3}*OEIs|o#2Fs0t%qkN6);M>233qXKq)5k%^HF^E%wi~ zS+^fg><%k}qDZou0(^cX@$+4BM;&wLa)+WIb%qMoQGjs)g6akUZOyiSyv4fbTw?2} z`dEiA2m-*73I3t2<2DPtV~Mk;ft59iZyg+bbP11yB>IDCD2fQ!6$|Q?&tol=ifEx| z8Bz!Vw_OFgbRzQL$$m;=V?$Fi)Z~MZMeLeB_wqS3M?rPp0cdmCZ&~i@a3Mvn2{+tX zPeD_`B4Cy+aIfsP*cd2l7v*FJS?$#E{i9!blkaNc-nr5#@N%}s$5Sy0nbm~JPF5|` z*D4Yf3HSF$AKxF@J50VpPQ*nmn4;iJ$CdmGsWC|}?s2c`vDmdKlehOD7=J#X+`qxS z!43rVb4m(Z&Rmx@>8$AmU9Qy&tPWOVPr()kz0?6$x^ZYQ`iFxP5AO;yjH$;kOySMt z9U`E5I^ApgY;G1v>Qlg2R<~eG0`slH!4uI(_JkE0Byk94g%lR($WyFOSUrP$@(U@o zQHi?Z>9d_YZ&3!P0&)+O`z5%LLY$@v(t(+xkWthq98s9i02c@W1qHb*^IBz6?Ik1O zj9xpq)BmlbiLQC}&)iUNLr_vMUFy1NOsXAkQhySqw`5K8H`mC=F9g5($HcA+!iu?8 zHPTtkjh@aVQGXiJ4VmjpN5!y=pWPoOBFGEU<^$nHjh1?7soO3SIXKlr zO6w8~_H7-0T|lers6+iig3)CFrND2DGz&7RJvDMBY+PyWiIdX{?mZHF1%oy(>sC8G zm_9xgL6p9Lpcr&^kN=k!m-TTIvh(&Vs@!*(YT|;332?dyB#1&mRPx{v)%U}A5hgJFO*o^K$w&) z58l?Q;(5z>!PQOQTxZ%PlDfYQIKiN3{4!ws~Mmpb_zh)jetG z!kvRUm&2rJdEd>g#MG1IWM4t=jUn2QDZQ71U%U6hgFU7K3osqw>$VE&rMuU{p_L}0Vf`l+zYFnS2FGGDpo! z5+4cCG^ZXD#<{11QaTz9Od9Z$GO1uQu3(be6nLc3*eim|G(nYB;|k3WNs8$i;h-k4VxQ-m99xIh1Cu!YI`_Mk>s3BoN0x71@M>e zxP_HYc^bljvquwF!6$@Oz`8R)d}3Th+)=~LN9FzDjMZ}t&`QZ7M5PfT z2$M8j9VXM+F8WGV$RtHk=?E^XVIEexji@x_AtdxP$ivDAnjsHovQJnYjF~Fb6Y_$% z_uvVHkda~AEFlF1^0WETz&`G)1R+NSJ^?bI?%Iq|&iUmC1pjQBR$kzb45FVbX51B(K;q%;;An zM#{+<;G9O*`Wpz@;nA^q9W82XNs{&*KFOMe(7cS3^;(8$dF~$^nXgS*lI6q4&N>~| zbf_yePiTNO#FMp$(BDSRoeP7*bK06UXHPkL^4!qq7*<_;nC`}5pbgB620+fkA{<^E z8f|Q-pA~xUiiwV{SkfDfC9*?k83-Xf^-M5c&@;#9Rbl^@^mR{!BRGkbX(k8^;6$b) z6q~wtrr!y`q6PEZE;Vz1`I6p9EN(&uGvOTJbbB-ajid})7vtWpj;00=d1>*&u23j! zYD@?X?O^~m!DBf1byq%J#BFJ>f_!M>}9pv=9<%J#*Vzo4oqmgTb&_ zBGO<>hVC_aX{O#rc$_<@wY9l{yt=S=PGEc@GZ7P-BFvelVV!{qUW$lxv&P{}H%D=m zG+`B@qn+9*UaisA(m;NyzFu?Mo2$uWvyc2#oym^2rYcfvZSs+yDiUmJ@RFZth%d}y zYxGh<9`4?EG!l&)k#KPaa$C&3(_C24JhMoBi?V2bS7U?c$nmqoqvHq} zS1hygSzF0CO(Q6ymS?Ig?Cxx7_MJF=o*3jveKh%DBhyHYhGoeUOi&bs*c9!pjWgaD zh1C(lDwqNnhyBYJ_fUo^`NH;7)_b6KYqW8nZHj_n>E|Xi zlij`V7&*m=5DpFn7W8zeNaQ7&Xz6z|41M_6neosB5klf-w={X{G+ahF)mr7EGpE?2 zkDodp7!MI492y=YQJGWcE{^(xMj@mqH9f#^8T*#;32~LoafYUw zd>)D}KV(KxwB2UaEo>spWS5iuT{#kmM%JRQ6hc&^&%?5GCDa^to4&;mg1-8?dXJ-W z$U}{+HT64e^m?qe@^MEAwF?6ysaP)Z*1H?Lb(Mgl5`x1cV<2^L@AbHw8|!7cB7$SV zi9|v)MqiJe>>mw85K5-|>KM&1PnUrl<0Ze6$7XD3oaKKSwY03ri{^J$0!Ukt5W?!+ z4$I^*%dmn61ArmardHI+N;QNK7=f{6jckQXraLq#YBiEl4Iu>jg`Uo)T-n^!>xs-~ zvA&X(3889^raIIl30Aa>D0|UlS?+9aYH4mjnkKCLDzbDfQnvo6I51O5Wqk=iPsI`} z0T!){m8uHD)pUZb!fLH#W%0H`LAYs3KEhf95(~#@GKezeFSEiNMMY@BvR%?DFH<_T zrQ*b!>V@E7!7~UKKeg;XVeSEhagh(T>KY=KAk4=fD7uMx5d^cUt&^US@n_bAN2fD) z)Ko<=RZ$&Mt3CCQ2-1Ghf9TYUW$qr8EKX8@kk}N&(m|SMWsI-j(wWq)M5JJ(dMq$U zB}|PbnDTZY4b^OD3MF&N)k-5-En%kewR0@2uBkLN2-8dv%|Tv7`8Nlg%GA^eD*D7# zR3Zwh>DvmXksn^L8dYd^L9+!bQvXn5AdtWe z)6!&bby5^GO3hT%h;SsNQzMZmU>3AkSxOlmNt_=^V47}ivA4NtEaRM6Pqjd@GBP2K z`V-?Z+2yg#ZQ|?<@Q=if48#?d?e4I*yJ#g@ZZOqGgevgPct{%dCjt@4=Csah;hZet z8b(8jlYxZOWu50^vs}MI>RKg;gA;;Ea6)7q*6vn|gTjLofDr=oYRob%1h^$ z7n(f8I1~|v{CprJuntRCi`52{^TEXMI4@$<&}f_EVL1xsYQmX2V3mZhI+gX4a!C&D zgtTMF$oCJ&1h3;ecetK3+V}2x@CQ%&cP1bsEA8!x&)n9~ zX)|HBPG;^QibA2tZ(i^})~|S2z~kU`D?ER)-lO>APi!7~WE5;#qWt|079mqb2@9)h z9SrVwFOK~3jN-NcQ4VcbQU8E1@#R^5eO*J4LWXqh6-a7%4 z0kmR^dR1YiP`!)Uw9xs{HTF&?_40x6$&;ZyfLh;eyMGCLVkrK~(TGmg;1f<>MhK9@ zMz5Ric<^e+5+D8A;pliE{P2rJ0-@K2d@L&IY$e<)5E6oGd+iUdajt5jw;YOuB-G|( z-nQKJft6MuD6bxlfFz;^m@;UlrI|vTm0r_re{ij1T^qCYP=t3e*Y!C+zRB~+t1JwF z+b<-N4`sHPqs5HrY?p0TTsU7nJSNh?^TU|Baf$WxMIoWNGgqx`xQhIn$p8Acu@iOd zhF+^8D-bC#W9+~*fLDIDAyg9l5TM?+gg$&;5ET{J%j#xYq3D-jJdV3P%sd~Xlg-lJ zlQOBGYx^wchQ){q@FxeN*h<3>Ugxcgj=#QdJRx8aV@B%>mdY=|g_)EDiD|OmyB7Ic zoU}Oc+DSf&KwwPvTIf60d0T{uKkf@oBvd4%EQcA`%gvq6i3=l- z9N?{-!J^fDLZ|^M<|Qb~l#{(?RsGEt&fye?MiRtbV114c`;_50xCjz91}$#l6s?wd zlJ*`m&?l@8#!UH#kjWg)k2@-3fwSDXnsGXX|N7ndL0P?JN+d*t@rC~J$1mX3OW8#Y zG#{5(4yAENlYoH*a7SfDuv+h5%Q;>Azda@nso#x#&8(Y`|LT>1Aj2D%*_SrcSdsyQ zlyofy!1)0)36rel9V;29OZfgD#&;#Kte{p8@DEBqdSS$+NMRPWl061w1{?bmLR=Vk z6aYRB8ao_lBKpWHp`$_kz_m`>K#-+Cvjc%)Y7PSuvMdlYO@(+%=aE4rIrkiD2~gi+ zr{eKvUkYx!DBZrn?Q5}p+AU)k+kHY1^klG9bHntlBUUDvHJNt;c$(~#z`yiL;FWXI zjaS#ZA?@z8ga^jIw>c_X=v$UJWKjYXonAsMB~2+n(B-o+vhez=B*TV1J*`V2#Cu#o&O+;(vID6 z&P#9Tb*NZ^MZpJ|TBl$cvQCCPQC`uK6=i(#SUg~*AH1>g(;IAK{fTX7_NQAbQD2Xf(VqA(bjseY4(rD~AuaEtHM0wve_4h4xin5CG z5K`e2WxoVi;w%*n@kp}>1{2S02|Rp8B)as$)oxuYt z%GX) zx!jr#3ol48^Lbc+TGlHXXf@RALUP`N(Gh|8Z4126W1$`$lTHLAt@~Q0EWB`yxwa1! znC!Lyt#$0$h}dm`yUrvq3m3F;YH6^cLMmMXKPk>54V6Ibu^r>TJ1=!LF|D-x+PHE0Vek|C!@I`ux_K0eCUzVamUKHgLv%;iv6B&m z5#i-MV?RF3+nw|hH{N|@!XYXb5h5OJ(2t`MI2R<2t>$^mfS#?zsh`QaQ>T~c5+!gK zK?Dm4S)f$8k!5kqj!1}x56pM4SV@3l!zX!PPY|*enWE96UW;c-?7vj*EL>v+dvha_Oi)G-WBLJdaziS1|O!#t!y;*YkBCq(es zW{1a$h`~t~K`zRkFcFN6^KYDq4J058i$B@M$3*b%cC=+!_1X5Gi)}w1nbU0T?s3_R zBa3L52nlbVj`b&CJCc6BJrowfeeLMk5#@0T3JFX(nEO_{6*-Ax3So%~WwvTUTs2Ez ziY|D9VtxNj%{+uwwN(qXZe#N@1X}7?UQFjXLgoxI)yi(o{Rc!XT^aWfqlJsfY?y zZ++nHt+&gneCf>y8ZB^nT#E7vq)?rmZu2mbgmbYwW@}fbX*Wl+az_hi+!BAGtwEJP z4P=yRX;#xJB*oCI1!hdLQXmhhIgM({huY18q1&684s6lN3ZW!tkh!xUxC=4qOEHPi ziaCt_5yGdeVTWrOA1KENpOiVq#>{h=a(4(<#9UtH;dF*18R%05nLcw#b1%=cw9*}R z#^~h}YKm3~)zhr{v62Exf;^-wbi2(&kW911T$CxwrXC@Mfu1OiYO~T=1RLww^vu+U zGTR^zlRH+ARFwmRU(pEwi)s>T+%cJFBd6LkRBRz<$YSGH;QTS%QkayKTxMXpMPE_c z8iO2UV^xbOg&b^LfLfZptg75x{N%#%x_m2^dNdW8JcSA3S)%-M79^)g+AM^H|G9cq z6rdVtf(E0s%8-XuW70quRIlJBPb8&04>|R3FkLueM58Q^*OV=hu0*K@C8zG35%8*gftVxooPGx-+wmNa%Cn7P5GWKdV0^MO?f}O@@6cde6nn@&h z{UgLxj6`E*O$+nFqGVtTQ-CO0^cjW`1Q92hRKk&Hf}t~j^Dqm1B?F^s)*DI48A2`2 z3pjaCfg@zfNKfv`;J*Qoi-KcYV!zpUT2#$reHrGG`i z`!PT>j#G{hrl3S~=&P?-EovsWx88l|=oxInF9k+qCqvB>)O$i?O-GCO$S!R=`s(XW zp1F{2t&tCgX%ZpKEK`zgtkx1KR*QwaRA28teEe)uLQJrwCd^d`^5R@2zgLo^uDNY0 zlis@1=LXK4A2c5*8E8-=DKpQ;2rZel)YhWn!8CZ?=PwLjxHw{Z1_oC1L8Rs~uLcF? zcX!MV*_+ZqxI|YJ>(?x;;>1=hn?D`vwRY9wDpFgqq<1=)T(hFDiqw`Z>H*}ZdZTat zJn{o-qm*%pFlGja{Rv*k_NZ_^470rMS1N?v+*nVv)~J76L+PE|4K9Y|6lLOmv^M!z zmKhoe5Ib#B&u>b~DJGj5>TOoe9|)=_y@f%`z*H-cW)i}dCNIaa!=q!dIG^qA<07VP z#xB;FwA2LPN)*B@%e7iQ;TvxrYVdhmR&!-O8rM`ki}@)Ym!HGFXEXW%Q{GVaoiF zp@hi`10&0q%}W&z9_(7qr5lX*(m6f2_8x$}b?uU_8T?Ax{Xh|6JT zOvqnG;IRA%u7>HTQImne(Zvh9=6BDj3Ba_6Wy)w0n%(8NN|*H(3%73dB7`ctmQ<3G zKsNWP~_Dpsh{e z0$0QQfUsKB3}ma>9u#LR|CwUc&0GZU^psCI#{SIB}DCq7X?{1wo*!EgcM{F<3xOHD8lnt&*gwBwx>8g5(x(5qJonE zMQPm8;()FQStX{A3iZQ>f=7J(YwOO z7&YP4M+=2;XyPZY2X_Y4ivWrqzi(sxB1hu$Pex=4NRrafV13^@?_zIJ(OMO&FB*OH z<*}D9$chSlJ9g*Q-kZCuf%AdyY>D|5EFrXMiRU9%Ie`HNe@X;y707fV{P33G_EEKO z6Ge^RwZ_^1=EN=;BW=NAg0g0T^FtfFOcAo9!qmlLzui3ce80-1g2&#mw*J;R%0thD zcE{A%kk`$AaHDr&16#96FRd|pSs@Rzay#?0h@cb_`^UvGjJhmH5tK)FO(YZ`C8S;3 z$9C{IAt+MmRY!!ZFfN`C$rAu}TRKlVl_91sWNvLs1nVcQ+IFH_#vUSbr8yu z9RfnuI?s8}1a6#H*T6>p;n84-mt%2AQE2^Q&%MhWP{9@k2{L9WqtyT>qsFw@Z(AbW z<8^d4ieG*-a4;-iI2{|p@ai7t16R4pwv~)V|o{&G<=5BSy{^^nN zixPOp9qsq3u*Li{n};3=(#tyS8t{$*?qM=~IT16>wp*4-cetH%n}x6ccI*wm6p2$T z0=@Gb^PRD}W0G2#fNLu7iZe(rQ5A*Sb!?JeS;RT(S>PsK;o;rUa}&z^ZkrsA{_?fN z+fMgiVr87^GnL6Wrh&Dc-^tekJPzyyoI>-)$4xHGVL zj`hPEJgq9oyksm}P>JT#6|w}n=DQh)kM53~2`OY-$AfW*!M;7N>=v285@Y>zFrHh5MLEv9GiIZl)P+-eRVs-^Af|NbfK@- zzl~6pa;HwH&FxW{Jh?Y|JRo(?am=01_8bd7@!IGkGMwAQ+`h;nixQ-g*gCawg@lx* zu`5$fYcxEGGXu#kJaNeX&BJkz*ZRo~?tME#2N77&Wnm?p;FXaHK}r!UA>^n6*UN|Y z`@eHs=xnimd_$eD+40^*wvSz9zqXCqb~^sTi8##^J2^-bCh_Sbfo~p&TkE(_ZS+_f z6%~9*m+Q0Zoz#T%!pWFGl?+X!BASWv(SxJkI?6XUSU<5|&FqtS`S8J5&_yq8w{Tb& zVVsE!Prd?bzZw~zITHBBff((y{>3#OaZL0pptI4stcAu2e0qcjs3sSeIHXjuPdMGU zqiRK|C?D82_Lc2%g{A*wqcawaor~j68{Bpz{PF~dMZ~>lW3eRCO0k#q>bnK`%{TpD z-z_*D^heh^1AacHFpCztR`%ML&tnje6*(JyY%;y7Zf;3Aw14d1w?=t}CdY&r7DQRq zS&KUDbuNn+;e?ENDXW}(>K;$TAl|b(@U@)@&PIP?y)!&c$l%~a?8%dIZ-Zr1w?h<- z*`&CDqpwMza^&Fn*S5tb5cSDTP7s%l9g7Vp;O@2c|K~b88k3$o7L#eU01V(6h9@|? zQ|#y|W?m5dqNM0(UdRKQp(3Y)5A6+~jiX+R^!;asc?EprM$bDel)}r;z7lv6)2q51 zE-IP%ZMaJ)1fW3329E}Qav(M=Ku1jc##2LN9_#Ax<0D|P6P^ny-Zo2Ln^jSPj}XY< zJWa}E>|E%VdnZmr;X+RS;d8@r34Cb1WA0$|n@?Trv4Rtb{!qK!MQ5LUh2bp-*@+Wl zKiwZ0;L-fJ`0b~MWQaa}NAt;jp_2^yP`lN|;zRHx#@~}%YbPFIG5uiYnU5)tP-ze86^gDH2b+=87p>n z;}UnLprR)emI?XpKFj?!{_unxQ}8?2u{SKR%c26y@BDljjk+0ZkX9u+T9rJh{7ONQ z6??P&u6Cz!OG$zvh$;}}va5W{v_Dl;crzRjk)nd^QHX4L zUftr!^w?BGp-GvvHrj4)br`A1f&v%TeY{Tw3IYfc2~;vH6@|b}$U0i>@0?>Qvmlf0 zyrxl=DM1kxoWWnpL6FK7X{*on_7)S9iiEqH9M=-VS^W^wSCW}Aji%o!V;T{-%?aFi zviFb^o0sLh?kvwM`A8M@)p^Nk|Aeq)gox3@_JtC0CZ#4QDg2bw$CK?nVDgwKipd-Z zKwi%0)58?m$*C!r=X02pbvPZMVeO`9&N!d&Eo+XJtqJpV7sJ&+6U3Q*43$70W^ZmI zc5PW?&2FG!^{WhdSg04p)TW5pRjpM~Tsh`wG0{<6-Y&=0E_$dEdSISY78loBx1<%v zRh|7b)@IZ+;q>ak>S)69{XpZUWEsW)d05WSFz}KD8HQF%;H&X)(qys9B+XVBr+JC9p=Hvxt!^-e zLXozXCf%=lb=zJ#CDH~!(F`U{8Z6WVX_MB{$+waHsFGbYe=gAQ=U;qn7uoKH6q>Xx z4a^{%&}e$6{#E@Y6pl1_Rn#_$LR)qmAOZvOV-R8!;1kY5F$HE!8WfrccXzexFK^zm zm&!;{1`#w>3T6HlY@BlP^@&Jy)zSquO*n%BGgXkno3CG+w{>f&hl&R71U9W(F3WNi z&ni}H&UE87tL2KB1Suvru3aWg1C!TpTs2t&U};QNrtoWHT7(c+a4$dQ;Mi(Da$+(ZHIu|rc1^yOUd3W znfJp22DW}FXow|(d=IVI8@7EQE)gCbvbMt z$Le3NsISDx`)xLBR_h|uN@le^GBjq&v|%wdr2@d%n1w7j#4rzfH^`!vR|L{ulr@FiDQbM7mw@SVq0r9A+rub7VMNrR zEHCKkbh{k-&L#)hnRA09fiafjlytH1RBi(VQ)I)K=@_nXG84;BAwAO~zi6_R$3USV z5_qw*qq(KYN0fc8I@G0?7y=ZfR(z%aoqLozDX<%@)#=s>qVDEYsW7 zabaL+JQUV4J`|9w;7fKnQNKvR&@2*2%Yjru`jotlxU>(ZF4@xiNza@_hPN3yfn{2A znR|&PyM-CMiLkb%%VYEAOWn&Uxq_FbY2*|e?+lI5XXHiB!nMzBvs$@y=Nz&H z+gclFh6;=Yfg&S-@*W&%$}pqfKvst{Q_JLJ!g_M$VQPZV%yzDMpuN-hA~ZFb(>0mt zbyHWlOuIGWp3takZmY-R5+!NUGEQxfMbQ7N|58au;mZ_Ks@NRGC4zh!4J0CmRx8`v zGbfqjjBzbaQ`r3><^x$YWWd*;X?jk3^Sn8&V3x%O$z(N@4lp^gcv=EgXi17PHUpkf za#*@Kns08U%{G02=^IN&f=St;H6fg0>2fVDvSv~>Zs)JSQP{jpTck#PSgnlsOZZGH z)`VKx8ZK!Y8JABx*HNwn)}*mE3U49ffo`-*--_mF8JaLHe!y_Oa~U6B5qM8KGV*hZqPIDU5*3zq1(@wQfJ5?(b!rD64Ms1YsA5Hz9 z8Y+5i)W)=GpqdcYMr~a215K_*jlEoIqc&=zOkG&M5Y`;I+Nh1Hc!gClM{Ckp8?|vc qG+{}iTpP7f8&kP;laBvifB^tSi)B>}Ib&J?0000

    &uY!?~`}yxTtU#gbVKxFl>Fd6{f43gX->tyZ6OeTS;YRiMelAD; zJ4E~=;LE=Q?TA4UfBOMGb8xG*{9TpE|Fp`Ho5}vqz&~98fg`AYEAPhstxP=r|Bd{e z``=Q__n(Re!M~ON$4Hhz@QTOPXq*BI^j`^qCxaozKFlSEW8{$vk?$Een}SGvzE2GEbD&F1%8I+b@b>Gf`4~q|5o5g z=za4Rz=86955eDfGeO&GH&dL80aoJwr%mVv=rl#>^zy*<=Q>{ioWKHn_90wqH@+cs zzPjW3FFp5Jl#0pAw&Y*H!@Vy+3`_Jhgbr6wY`fXqL9~waJ7jt+(=jsQ-QrkJa z;`h`bHSLD8+B<`K&!9W1463Z|md}Ws6N?In005LS6S?^L`-3h4o7lcGe8;s{ z*OLrxji2ToF?*PF)_+1yKymZKdh-am4-Bm2R>)I$S2&eeRL$3$cP|llcqJa}U`}5| z1zZ4{`?CTk5d}UuKh1TM97tZ*zRZ|TJYtdnnh5~=PTJ#!kN`g+WXoCxCar%G+ov^v z8j`VdB13zjgipC1)5w*S(4aG_}%`z$sVJKy}=$Aht$eK?X54f;m#Yf;1gVZXOABz zS0AXsDOT_*biDPD5)bx6znpQ_9adsf9!MEm8&)l0ppP5DnKrrOYn1WJ3&0nPp3x4q7o*W9P@PF|?y%sK#|Q*Pp66c&?tU2+Y( z1$1^ARl&TsR*KAj+NiRW!UlAYS#PFY(-Z?~Pu+Ssjt}Y3(w2ni)#2`Vc4dS??`8Qq zDYhGKTAKb|0d>fKjH}jK6)i2%G@i5JsmpK2<_c8olQ{VtEk_!-Q>I+- zDDM*0A%cSL^A+;dN54Te>_Vh8tDucz{Y1U`VDrVGTvOJKQNmCKA;)74Zb9AR^5yzL z$07I{x>>aJu2o0@)tfTMlor4p$)hO73DJ}I<>{z$APryAlI_2s@fSc--)p0K7iv7g%?&IgqB*w2v zMQuv6`k+l~0b!V9s~`3r64X8;kF0CI`mBh3ybXG_%~9q{*2QJc?y61WCJa!Hf$U{ z?U2u^&rxAV{%21aX)NhC0zB$X-enyuT87t6P(~4+9`t}JahOfPtVC6WQbXGlmjL=s zeXyzfPS)iFuj_(EX$Dw@9R*(yaEaoW>Wi{o0F^Fs)ZnR_3;bVAwn%n>8X$z*eGU;L!B@%}t;C=w>x_a-MRH7XpBE^JiFA$Y@Aj@kcHaX1z+U-WcQENiOv8rhX+}q z7-3grX7pzbm)*(m@GvNh`)(cEaJbD=Dau6OB#{TiLW@b5Tj4WJs*daHfBN}K{BsET za2b|b-vO}@2GOPu*D0K)V06u?24&HN0{ms50k@iEleu)|j-sc9&3B=Xk#PdxV~qd+ zV5%-_(s}m|f%4`WSZeyeoyuB^7mwuU9?Ch17GGa0E}Pf{xj0ei7Eo-9%ADk9P&&=0 zS6UC;TdI~U5cv~K$wBEUp~K)vrG=%g>86sDQ8}Gh0Z?K;1o1GuC(Nm!$LE{L zkeojQ^12+9Hr#2?8A=QC&HE;p6ab%T_;5eT00~IPfLq>ERR;d4s>Fa{Y3b7=$%#u3 zj!Ptf9rF^UnKR4;i;C;DtvZDIgx?8KekF3il6K1 zc?rGAW@}N*xLE9suncF`&`({Ua#Vd|xf6$9QT9W`3MdRE`rvz7Tz31;XXPDw@Akyf z%N_SG5A1;TkDvNal2KZRSwD4>`5^(C9V1V^@Rkd`Jn9RU6s$G@Skt_3T2w#iZT9uG zP;mrTQ^(N+A)#20#U;YVHN*O#8;-9eVMUf^H17InYg$~;_IJn>In(~BT~Z)R$t>L2 za0RV63iTYZ6)M!x@Vl&iX$T`<83wg2^-wp8Sl!$$7VXf zco<+4e1#fWr?#IOQ&N(%)-L_?aN12Ml|e`oB)}&W9!RPmamLs8J|N6RhTD=)0OwcDXH^@+K>-}Ubp)0;)ELA zFN70jMn@_UOi!JWlLa~7d=Tr+)srV->O&*2YTq*DGy^{Vr`w+E`9>r0}i3w8Bsq3A74jwD|n9rrafh(GmS zM#O&3ETK`!&*53(5f+Ry%DauHzl!>zMvo8-xtgieQAcKnr2h3I-g9?xVnX0(tOSFa z-LQc&Rn4^n`HPFWomaKp1=7b1LUsG0VeF+GPQ%|}@b{BCDCfCpe6KCcj|tqmcrhib zFAuu>4;^@O#zgLij9Y@~ncVzGQJ(h3R!wxD>C7>xDxH8zM|Kzp3EOAih8>*J_(|5{8<{-n9o= zxv;kARtl)X;jJyYDIrUdD%!L)*e=<{ZdNVmXi&-5xJ(|7(>T}ViSw}YJ$FJy@)??P z*d`Vr$#yhb4ovoEa+&rkzNzzF?{Asl&6MKx1%~`OYt>ElJ{{>c$ibFvYaZcwEcOHO z-DKtqMe=jdkMd63$-u~`R-T{FM5+h$p&=ma;RB z>GgO46_K&__T$wkr(R#Vdu%D-CUe&K*zZdzWx2W1S5w!+9a*fx`s!~hT&ZuyLCdWf zQ;2r$E|%An{eXLFj4Ezz>A5)wkNv|{W#zjfJJ#g~U>}#JMUtSmkjPHz72o187HhfZ z{l(p0Wr?0hFVyfylEJ=wNj8&eR?8q^_>ZYyfKt+nUq4OXHX4eaJfm?RQq!o+asBic z4RX4UDqb2vW-0rwF;{&$X@XH%ERztAk%%VNU{#^jlWgrCv3oeZAr9$@xf;I`DJBe&kPd%HCctp~<}QKuds0{ZLGGo1C!+8@L^UUfTa-e6Mec`gc5*4i9Cga{e_ zKzJ~PJL2DeE)@$qF~B(*+^*Qs%=C0w-OJW7dENZIWskZ{Jw-GnZ@8qO1MsOom+ATdbFShze6<4xWz4BH8tw#0#~c4*v#tR06Yy`zr3{Pfg`SZb|KAzNM)~B)03rqc05?Rm=?Qo!^{zcojsgvG_ok|ir~Jx zwiAI6sbgcGQ1jiRJWVXKJE=GHDB=j*Uk|0nJ6@cE-y58pkgXxhP|bRYl3}4p-eYl( zm^0y#5gs#*Kb+3EJS6v3QY}!R_4TZ6D$WpzF|%;ISTl%f{fs5&H55#PK_0)_?aA;u zw;xxj*Rv4M-avV@@-UovG$-I1JMj||A0z)bEi3kkw`rwZ>l?hJipe2{ zCO)Y2T#3twpnhIivAyBP(X-fm>yM1kjs?_DO;y#qs#osXwkP|+w>RGl!@je`O~-{G zGDJ0KQO6Tem9DI8n$k*qa414Kbl5hVzN$F5)BGK7!5%yH6gxGn}500n|W2C$9KxF zbFJRNMY-UCmiiEp`@mTy=H{Moj>Hgfoy4JJvpW#O!?78}jK?>Q_3I4>q`i`5WpYhAFTKG=Vax)i}? zn~Yz{S-xJuRASy(WVl$Xr@#EknU9j+lWvHJDbc+;!$_PZKVhcccL$W01L57hPutF> zQTp+{FSFce0`{P30{ShH+$G(k7=W4vA&b~$Mq385DTV4T8)y|xyqkHT^cU^A&Guq{hHE<3=j7rCOPs#9{GY!vcGgD(s9-pmbq;M&T!?V-z{ zGW9SUApqLnoxA@);|-iX_OY+M2s?fpDm)+cG|7JO^qB$6ryCw8<$k(;jVkdtT+FY_ zj!`sDw_OX)l}G{cJhsmQ1IAV7VyFtGI!tf3QTvc^<#nXOhWazLHFPJ=t{10`W$}NP z>W?~PDw(q@ZIz5W|6U{clm@Kw4r29eHcqT%alv;F|WEJ=4qebts(9%+3!p zUY4h2glFh@dNdbtMZIc{OJa(r--!GBMHp89NQQYvSI}U8@zzW+M{~1yz@350O!tv*ZDILW=t+f(jduB-Dbw^B zp2rtkuwj%(zir<>T^)teM#0KLQzhg&@jT|hVe!Pasab}G>@+MmReH*jw{+}n^Irlh zU&g%^L5rH?lCkvW+}Z^TorAOl2xo4)!6ouw+A~xdp6m4lW2WyWu)PkhsM44$RzHiJ z#r58PEwg#Fd8@Om+%Y-BI4Mvm{U>?2J-SKT*G*OmRX zG2l#6L;XeVP%4A8yUMK-4b(YTV1_R{_`en2TyN{5C?p;Ph$ zKn{)WR2orzzdJqLkox5?^$K+`?}@VEmkMz*u!H}pcFRECD#3i|Yc&gcr0a6F-PIBo zdWzjH0*U=dro=dB_C@wQ%{=>WCV46XtpHgrYnSFKa}z}wJ0ddf=|Pn*_0auUBoS$h zWtmM~W$L4M{B`Dwea*$u@Os6p7VI~fE(1y8MsvP%4CTxNMM9!|%m_=R9=2ok<*5zE zrAWX$Gy&bdlCezb&PWujPrJJYnpv@@O99?U5U=)6OkyXx|)PH+u=_ z51Frh7N`3P*~-A9`$bhGTcW_3(hko!IwvwZCt9AD5v<(upo7bA2`gqJ`7BeVx|v?R z{tVRSF9cR)YF71q*UU1Q(Zia#2iZ-=7EylXb6I|$_%D;7${dn;+UJKFEO27-NO6fp z?N63MAYwmpx+rvfI!2C;XzL0>HQb@48fG>avZ1u&FO~eJE(%~S+A>vvdc^XM-YPf* z+3&Q3B%BTG6%_D~UOE_E^&el_CeAf&6{g}xA&`T5S)n;~)74uVC zlTSQ0+yYfBJXj2FTf_QZmd~BPW8C-s$$-j0tNhhi0UZxYp?Xi=q%J|c`qh1N;#ry3 z!%?iadz!jNErW$MIZV#NoL8HW2$W<9bv-T5Y0xx$aO`(I0>L>{V9B#>uGhvPx}8@t zY&T=v$VtVd@k{;QS^Jr!ZG|xUabE@_F@YvlK zt|Gf4K{KErEgiP)qCsMa0Lvu8zSN={r*cwcli!B-K!dSuIB3Pd+wns(m#T!ADaAg4 ziHt=^jYrkLBrsRceQO0pY}E38y}bA6t0U_h&C=(0+=wo z$Nzh&_b7ElsO@Itqm5m8%vyU%1|Dv_jMRKlv~XxZy7Mh#Oois-~?P;)+g_+kuF>~Q3I?E2M_CvoczrEc<0?ceFU55mOcyU*rgce&m*#TLq z3wjI5o`g$FBopS|s&WnQgfv!_7rW6tP#C8$)qqRB{tcC9M9@@@I;stCP8}!18cpWh zO3h8|N#EQ~3n=nHRo^PH#FM|yQOUyG)R&jL-fntIO5@rKSP$;Wy+Y>q+7>C63Ng7C zXR~<{gGruwvMpQ?cJOLd#b*lDzE_w{P?SI>&DH_)9I)#SN445Ic5U{SYNSo$aqQ!O zkPJ2JpRev%Wg?la;vbgT({rW!l7Eim?QE3=NbYD8 z0i6^YAmS64dSnqD946 zQWsAJJX{#;zaBR;B3-M#8B|qa&}jA=Ar-@;{?aFwXZXydIA7`6UeNA)Qy0PiVqrp% zbmV*%zKns^sV*k4O_PNfk3NSoHevaxs}E$2^n9|5S>W$L$8 zZ>dB1bn9a+0^TLz{ysk1!bPlP7y-4#e0jT`Nv7xR^Ne0}i^Pm?aH8ZbC76qc7zbr;?!DdT&s#s|2 zJ~(1MyNo$00@XJaFEPDZW`k!MZ+#!$q*ULwDp+$CR&-p6VkG9~nRA`CR%I>Ae}{&r z2xk6%TyhLBPUvVz$M_Pg5nP^>P20BIyki3@*PhgODqP{Qs4L$cXL|udrGAm31WA_T zh)#W%tO-QXZwx2FUTH(`IoJ9G+V!jA$EQetqY5>F1u#ClX;^H10cE~!xqxdB^uAdx z*um|Z)Z2KRKY-?yRp#eZDs^it7avc4b&JJKj2tnvx74-gSGnDUUX`fjxqK&@WKLJB zy5v;p6f7uiuW0gGPOYq9OcT=hh-3Azy=AoQOqNpL_Iy}^7>vh~mzQtf3nw{$TvQo} zels*&RqM9#Tkls~+rx@xX~XQyylSP+>!68pdA0L!qDrjo*_LSh9!A17Jv9|2x8Y`| z?Mt&0cM@tYEyY3eZdbkR+f`Q%(sGG8NU2hTrDKXvq*r8DK7`Wq>Y-s?$-1(#QoT)o zaxb0^#xG!7aq9+S&#Gj#uFb7GW_<37GZ9C%VsTMv-f}eAkf%vuFCuqcTnKUEXM1`rmqCi>`L=#hsc+!R z#if8Sw-33GVXQWYxlTLHqC)tgzhLoJ3R#Tu=1tYo&N6Kr?{Fbnv%hC@}){yGQ!1woe1b*!B*U*tvZa@J7z^c#OTu*moM~{Jh zhwDaJT3Q`ysBb%zHbS z%&$-?lPT*q+M5m!X!Lkpy+o0r-8}3}w;PurdgOeY&a<`sZJEGLUwugZbw+}~exY_N z5?^Dc`l4HGt>Q}+rfYw?$8+Or)8G=CY)5s--UgF5m*G;dVxWc%l-h8!Scck?`{txc z*cn5qxe`PBSv(S)41Uc)cf3%+NYYxz?FK~xVmg;WAH2z{#Z@2j+b-@Ou5=wC(KKF9 z!4dnuN|)W)*%pNr#;m|JM$g0Fn+aBu86KY6%(|N_Yfm8`Bf-y;D@~cRshWtqyZ-9k zhp%^bh3AGhm)?uzdUzx{EW?)JJT>w2sbcJMF5^&iJ$dqr3AB#>mkEg{MBTbg)eLzTiVp&qo6Z%cgkUOyS!U(zTTl_`TgB zmS;MPGjIKaBC|pnf-e@t24xm zKZD_=c69})3^Kiss!B4j^7XalgbhRI!EQT#9Dk%=C2s&KxkQs8zS>u>*+SZ%zK~tW|>13!7U^5I1&y~3T zdg56Ryt*wvXz!^p2A_>a3^$nt#V7Tk1%Wmz5@y?npv6XWLaZ=8^;N`}P(Jl{1Ss%T?ZDXJo|7vlBSG?VsgJ@<#g#>`|zDgDbx~s{{Du?`m?Ig1^X*fA1WDsU*p04wR$# z%26(@?EVeG`ErEty#KsGa!Pt7$^{e3=N0+nXU3dWP=hw~M|$D;>BpWyC-EReFBI|f z{4Z>nB<5jZLF^z5U;UJ)jLILq%Efw(k8#c7yV~OAFdGYy^L9O<+K6WO@QPh@ig)d$ zOpF@l679(wuoz6UIZRqRC)h+ICTScMZq##Mqv8?LzLa4SdVq|&AUl)=X6g{XI`}hz zpz^cdX$1k{pQnH@QPY>(1LJ^F?%=~h&`hi24`$xZwXLXx?#w#!ilhU{V(ia(K@JCB z6F*uGhH+V^MSjsCzXYEHSIJt1dC?d+7$!Us2-y-s@2Rh>JvW!Be1-sc`q0(~Wv|0& z1?PqkMj`S&JdtoV?g}8WL;5(HR7s^&4g@#urynz~-6~>8ahO>Uk7(E*vPwHmq0@Db zxCIwSDX8*jBU4myZ!6r@(@UxGmhCHO6z67Hh11%?I#EzgBc3r50+=TgshGWXlpu?< zk-DV;)a{96a?!~du49rOwp-C(2k9L?%QGSP7K=NlF_MyRz03+Gr4g60WIZ)^d5veC z>YtJ6NSr$mSr5zcl_p{kn}5T)k}A~01FC|+aJjbx^<3sz$Cl*MiBWtLCNAf>*??w= z)Ua4gne3a6q9u@ly+3&Q^P~3m5(cyFjjekXiTtI3d9tm)WrBmV(S|}n>4#KmPR3#! z2?kQm`6=s}4jixTAqVz+WRkI}uW($GhiAJlEV#>pp;;Iq<+r_lJ5-d#Y;oX7c2u;RU$%A|R+M9!mypR=^%7 zL#)^OSbot19k5rar6=A55h1Qe!`hbX5&gwqn2D!+3$rVXcj!}S{+P|M<{MImYNX_^ z8U=-MWl8#dv+Gn$4(=n^INeTRFDm>qq8${HN_Luk@&jZ3S+3qhf-$@-@;64>L7UR? zA5}~_ROE|$T$H2n8MDrjSBv-sKDEc0@JN^_Zp*R#&6H`cRsC*jN?cw{G#AYG6IAi6q(Iz5#sfPw&! z)k1}aXpwdFb1ihvn!o%lzyGLp<)_q8mYxM@d2w{bgSFmYg`z$LnrjSND|s`(LeT+) zuUzrCx^t|_N<5&gIwwDMTKj`s@pzdGNAMI?td1KCWVp^XUPINoR=+{ayCc=W8>EQA zZc(UcWw!8{b5ym8T?>mrcIXOL$RnE`kzDr+W1Tnf<|Wwbd_fH32&3^TW7WP^9U-m& zRTRwkV%OcB3JJ(a2dq=UHh<2Jz^-KAKDgWs9~B+4t!tgQ36mwQ1NLL;^A(d-A?crU zmW7>XB8r9E*I80r7y`wSjC$0$hLlH%lUAvO@k1B;?T76wKeh;PbB2@r(RTOajIR=D zuL-4$Rn-~EG8zlhJIqK-5*kVC$(=#lRAEiC%#-nz`4M$R{>O@P4gL=_u;*Icj?h(% zI~6BKJ?)B^XRV*};~vGIVY}UL!>;h8u_G+1ESby?xGRiS`SNNLDH~yWNKgQ-mhXqP z`6q=;hWz1@n5RF?{G4-ncXwemqA0?+>NzJ_Ko>{L=Jl85)rGElk0mngXtuoRYR|~i z_JfA28K$o%KIU?V(*9>u{WXmVF_hmUOm%nLb99LYjp`L&1&xNn;S!LV9Um7J#qj)a zj5|KnVR}RC%M%xm+C0241$f*5p;!R{+<>L?fTdyWR`)yW75l$vn5j3~R6q;VM7nh# zTbXDPUc_W>1EO#ypf+C(s-&D-{VNofM9^LZi^l!|^Gb&;RwEq5FLI&hjy5Jz02O^S?XM=SURz$wT%X zzPZ&P@Z$ym91R+U3MIp8txnzlT*16UmS7|}(7U_5R(VKqH!kgtCBqPaeSHlA5b(Ws zz|ChEVj00vwmgy>>jyM6KtK!eJaPw|2F2U%#G%M=%c)|es+%RPt1ps$IZ0Oq3IdG+2+kR+c<$DAS2=_Nc zsQ~ZjAI_OYJu)x*z40RVgrZ1SLJY7I;m@(EUr&7wOo3|MFy?&!7iQ zAmtpwy-9D}US;Jr#;czGvJ59=CpU+KFDWmS7^2tcmm3eI^0Y+0i|fd|x4}A5B_E5o z&lh=lnQ)IhnpMP!N?bpnSnQ*beY-x}@Az2#deHQD6W*O+qI`0;-2EHC`jr1uy>8vHs&$!S%*gM4B zIl*kl>G6&{+h~36kM~?Sfg&9y+&X(+qiA8PbrVAba}@LagbE-({50@n-*k{siZ^53 z{=sw{C|HwLjLBK8eJ4;Hb<#wBCN^%m?!A5Swj=x58~OSU4m&TAXMP@BlJX_XcO2$q zqIGvMV>M%pVcBYYib<2duz6&me@~K)>dQ(fk7{gmOw7*>zhTLckK7VApm(Or+Im$* z+x`7T)!uH8Uf@3fv(KrRq@?~ub$pc?3j=$h5cLF;gM)Op#bAh2U<5b3;J-w$qpH2b zR>7urowI|O$f8r&96MqzYxKzQuZj1a;k~S4DPGLYvZow2z?pgO!rK2OuHUzYbf$Ql z@yjDr&%^&v)2s2HMk+84efQq?zxN^_OXyGjPYnS0FA{y;yWhFs^j|RgmV}o{C-a9j z{XbIsUA02?84=TuNvZEF=zDQvybk4l?kIAdkkQB_>iv%`U1?5l`Kzq-T0W5w>3`$^ zORAg(|1knM3Ga99$jtfiUxeG#hG*>Q5`^O`Hi{qCiwWG5Z5y;$s+t<); zn;IHPW&fELlGl!o?*3l{Tp%)Q%h4teZiyY>6Vdyltd4atMQTzmNcJ-O_H7V>Ux~K9 zAD)??**$EV=;!I}(&s}<(UW&Fro_YS&Ej+no@9w==5lp1_^rtD7_3u{SgAP|vev+{&Bn%94bvP{W>42k;jfwr>A2oNUJ&ytU#9)6Tna1N zlML#pdr6cnZ=HjS`?4+T3;w(#wM_+$o})6TSkOW8@HVovD{GH7d34I;K&IwZp4_o#vjl-J~SJ<7bbk?wOHNU%L!P@JY z-3Rkq09mloQf=CQR(0_R@WGdKu$Uszta~fU_0n1$;eRyDDJ^Vrn`TY0hT_1tXu?o@ z9$k8ZE;wM$H!S&J8X2Q#RHR+4gC+6OzRk;n#G{~jXfGCvdO$M|-Pm7OCZQ0yxHaS^ z^5QO%r=L?4e#{1`j5Blu1P$r+kQj47a&E>1$}Ca&5U`RGWWWhOi3E^!2`hq|;WF2;9r4V&%N(rcgvlOZ|b8%LtBM;2DFmF%1kxjfGKZVLv`>0vV?GPXTs_1AnTI*-!|^%&EJ5P&o`mk z(j1R}EVplE5RvnHFgodI)+2#af7sF(NvNIjsC#)nIqjk*z0SjAa2`4kxh_LZX$77I6vw!Rn0{hn}Yrc?KMJaH@) z+RiTjOv}brTj$8Td1s&AIkIlO$^38_t{iu#EpX3BP~|Ds+~lO;J5?MXART`WYHt@3 z;C32S*zXQ473Aob+!kwBGwMdV*7@>7dDN*~HLcipwOLgY)w%sU8fSidNDvyGps3vs z_mj^?r#8Q#mTz^Qz+Nuc6AVGT9~9nCdRGgNF_V_ea!^*VXDt|DLxf1B*@|D*MoqBz z)5N(eYTi39!EZ*P)9XXSv4BBI&+IBG&oza}4-}`zR*#uvtTwd8YxSj*Ovsqbw3SIW zi@znA_)`gbI(j~Xi^iCrB<~8NilW-9uG+;D$a!oJO-eOhBrnH#+&Z@>+gj<)YFOUS zI;duz+ZzyB{W*_zvxK-E42wuV;6M1EkDDrWQ}DQH&lgLrX6);zMLK>OyVyhvwU%#B z%JiWX7+OS)v{os{%jv&z5$<45@Z+k&)sWVj#liiu#gtP!;Kgeb`DkK$j(kDhcAMH~jR4ES?zSEy)#4nu;|z~1pJzH{9jfWFu7vRq{e zO!+czo1+2uqkT;TvgVHCMIgC)6s0_R}H>id!S0N*2M z9u9C#S#`FLR!}UZJnfz<{I#$pbxQ=<=yrq!>HGHPU=Xq5oEk6zHU-xU`4NB0)U6I< z7+W_lNNE?D?_yYA8T#qUekhLbS$9-+98#ARS{T)gSvdL5R=D-UxiRo2jpO6zeP25& zbIZ04EHu9$qv1-4?*d_L1LzOUTBs9q|LQRXJ?XHtc+6V?#8@3%CW9-JX zU_)(v0iTjS>ca$l(K58wKhMRP1BrJ(>o&W(lOSl|c22uP0G_%zcf9?bh?>0-WrdVS z!8(<1jqyzLh1H<}2g?14cpw42y~YoN<81l&vFk54qwlAExbL35tTZ)8K@%`d-{TVS zGWYe~@d!bMSXXEH_%<#HMOmZk=bem^yFX-GGjqA0+SZON<>}Y*%hDF`ZTeEPD|ZkO~d48&lHe_cddnht&O8v>S5w0`*ZQ zhoRZu@`^V-0)#CjCl^b#ifSgrMyMDjUzmyM+_h3~#~!1mW{ysNqoz0LlVxraJDV|`%%MqOPJL# z8@M(rK1gcmvYciZ^R7evW^HK~N0#ac8jmWDfFf_Q#6X@l?CHZbrfzWiF{P?R#z@TL zSJ{}}C!9vc5)+@Bk1bhRfwYWh&HNi&7lWQ>W=u8%Q52niZrI<_a!ocFm#bAs>R#b{Pb~Q zlcRlyDd41uqW1JhLs^Aoyd_O=G%83AC7cU&g9dPRAPkDV?k+jlpr>DJSFUgKnUi)Y zlyYy8>LAreIsnsX0Km`Mb}rinuz}Yb0b@J1+biUsge7_E4_<=G*fw| zqdD7l_q>(CIG{Bzi=iG;uGT zPmeVFY^L&OBRGs(#%6}i^o2dlOwrIrMc0)$Xq3=?KnjJ;(0gPp^}(~K+mV)=c{ZFbeylw| zLby)?gvPj$zp)R(=Vx%_2+SUNvToNf@u-A+OG1_G-p*HAxtf|h<>|&P3-^*y|YHwvOX9t<&aX>ctklN5M?xx>w`)m8C`_&%0AtP_kdA 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�f$z8esc9`WG-ENtc)SXb8JnIKyazkz@{hV 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

    + <%block name="jsextra"> diff --git a/cms/templates/howitworks.html b/cms/templates/howitworks.html index e799143287..28b67142df 100644 --- a/cms/templates/howitworks.html +++ b/cms/templates/howitworks.html @@ -59,7 +59,7 @@
  • - Studio Keeps Your Learning Sequences and Lectures, Together + Studio Keeps Your Learning Sequences and Lectures, Together
    Studio Keeps Your Learning Sequences and Lectures, Together
    @@ -93,7 +93,7 @@
  • - Studio Gives You Simple, Fast, and Incremental Publishing. With Friends. + Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.
    Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.
    @@ -144,27 +144,55 @@ - +

    Feature #3

    +
    + +
    Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
    +
    + +
    + + close modal + + <%block name="jsextra"> diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 62954b8211..8c3ea9fc72 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -78,6 +78,7 @@ \ No newline at end of file From 96eb16cd729ce65df69b3f70d4b8d95341d8a650 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 6 Feb 2013 10:08:20 -0500 Subject: [PATCH 120/444] Make local dev work by trying checking staticfiles_storage for both the course_prefixed and non-course_prefixed urls --- common/djangoapps/static_replace/__init__.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/common/djangoapps/static_replace/__init__.py b/common/djangoapps/static_replace/__init__.py index cfef798bdf..4833f5ef96 100644 --- a/common/djangoapps/static_replace/__init__.py +++ b/common/djangoapps/static_replace/__init__.py @@ -76,18 +76,19 @@ def replace_static_urls(text, data_directory, course_namespace=None): # course_namespace is not None, then use studio style urls if course_namespace is not None and not isinstance(modulestore(), XMLModuleStore): url = StaticContent.convert_legacy_static_url(rest, course_namespace) - # If we're in debug mode, and the file as requested exists, then don't change the links - elif (settings.DEBUG and finders.find(rest, True)): - return original - # Otherwise, look the file up in staticfiles_storage without the data directory + # Otherwise, look the file up in staticfiles_storage, and append the data directory if needed else: + course_path = "/".join((data_directory, rest)) try: - url = staticfiles_storage.url(rest) + if staticfiles_storage.exists(rest): + url = staticfiles_storage.url(rest) + else: + url = staticfiles_storage.url(course_path) # And if that fails, assume that it's course content, and add manually data directory except Exception as err: log.warning("staticfiles_storage couldn't find path {0}: {1}".format( rest, str(err))) - url = "".join([prefix, data_directory, '/', rest]) + url = "".join([prefix, course_path]) return "".join([quote, url, quote]) From 2302b40022384b37a82c59824f9c313920d7a6cb Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 6 Feb 2013 10:33:36 -0500 Subject: [PATCH 121/444] Fix static_replace tests --- .../test/test_static_replace.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/common/djangoapps/static_replace/test/test_static_replace.py b/common/djangoapps/static_replace/test/test_static_replace.py index e08c66c59f..63fc3eaa6a 100644 --- a/common/djangoapps/static_replace/test/test_static_replace.py +++ b/common/djangoapps/static_replace/test/test_static_replace.py @@ -24,15 +24,24 @@ def test_multi_replace(): ) -@patch('static_replace.finders') -@patch('static_replace.settings') -def test_debug_no_modify(mock_settings, mock_finders): - mock_settings.DEBUG = True - mock_finders.find.return_value = True +@patch('static_replace.staticfiles_storage') +def test_storage_url_exists(mock_storage): + mock_storage.exists.return_value = True + mock_storage.url.return_value = '/static/file.png' - assert_equals(STATIC_SOURCE, replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY)) + assert_equals('"/static/file.png"', replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY)) + mock_storage.exists.called_once_with('file.png') + mock_storage.url.called_once_with('data_dir/file.png') - mock_finders.find.assert_called_once_with('file.png', True) + +@patch('static_replace.staticfiles_storage') +def test_storage_url_not_exists(mock_storage): + mock_storage.exists.return_value = False + mock_storage.url.return_value = '/static/data_dir/file.png' + + assert_equals('"/static/data_dir/file.png"', replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY)) + mock_storage.exists.called_once_with('file.png') + mock_storage.url.called_once_with('file.png') @patch('static_replace.StaticContent') @@ -58,7 +67,10 @@ def test_mongo_filestore(mock_modulestore, mock_static_content): @patch('static_replace.staticfiles_storage') def test_data_dir_fallback(mock_storage, mock_modulestore, mock_settings): mock_modulestore.return_value = Mock(XMLModuleStore) - mock_settings.DEBUG = False mock_storage.url.side_effect = Exception + mock_storage.exists.return_value = True + assert_equals('"/static/data_dir/file.png"', replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY)) + + mock_storage.exists.return_value = False assert_equals('"/static/data_dir/file.png"', replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY)) From b63c3c5a676242e6a9c42cc2ca094136d72143a0 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Fri, 1 Feb 2013 17:09:37 -0500 Subject: [PATCH 122/444] Change modulestore name and subclass TestCase --- .../contentstore/tests/factories.py | 46 ++++++++- .../contentstore/tests/test_contentstore.py | 94 +++++++++++++++++++ cms/envs/test.py | 7 +- 3 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 cms/djangoapps/contentstore/tests/test_contentstore.py diff --git a/cms/djangoapps/contentstore/tests/factories.py b/cms/djangoapps/contentstore/tests/factories.py index cb9f451d38..e9a535f372 100644 --- a/cms/djangoapps/contentstore/tests/factories.py +++ b/cms/djangoapps/contentstore/tests/factories.py @@ -1,10 +1,52 @@ from factory import Factory -from xmodule.modulestore import Location -from xmodule.modulestore.django import modulestore +from datetime import datetime from time import gmtime from uuid import uuid4 +from student.models import (User, UserProfile, Registration, + CourseEnrollmentAllowed) +from django.contrib.auth.models import Group +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore from xmodule.timeparse import stringify_time +class UserProfileFactory(Factory): + FACTORY_FOR = UserProfile + + user = None + name = 'Robot Studio' + courseware = 'course.xml' + +class RegistrationFactory(Factory): + FACTORY_FOR = Registration + + user = None + activation_key = uuid4().hex + +class UserFactory(Factory): + FACTORY_FOR = User + + username = 'robot' + email = 'robot@edx.org' + password = 'test' + first_name = 'Robot' + last_name = 'Tester' + is_staff = False + is_active = True + is_superuser = False + last_login = datetime.now() + date_joined = datetime.now() + +class GroupFactory(Factory): + FACTORY_FOR = Group + + name = 'test_group' + +class CourseEnrollmentAllowedFactory(Factory): + FACTORY_FOR = CourseEnrollmentAllowed + + email = 'test@edx.org' + course_id = 'edX/test/2012_Fall' + def XMODULE_COURSE_CREATION(class_to_create, **kwargs): return XModuleCourseFactory._create(class_to_create, **kwargs) diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py new file mode 100644 index 0000000000..cc37d201a1 --- /dev/null +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -0,0 +1,94 @@ +import json +import shutil +from django.test import TestCase +from django.test.client import Client +from override_settings import override_settings +from django.conf import settings +from django.core.urlresolvers import reverse +from path import path +import json +from fs.osfs import OSFS +import copy +from mock import Mock + +import xmodule.modulestore.django +from factories import * + +# Subclass TestCase and use to initialize the contentstore +class CmsTestCase(TestCase): + + def _pre_setup(self): + super(CmsTestCase, self)._pre_setup() + # Flush and initialize the module store + # It needs the templates because it creates new records + # by cloning from the template. + # Note that if your test module gets in some weird state + # (though it shouldn't), do this manually + # from the bash shell to drop it: + # $ mongo test_xmodule --eval "db.dropDatabase()" + xmodule.modulestore.django._MODULESTORES = {} + xmodule.modulestore.django.modulestore().collection.drop() + xmodule.templates.update_templates() + + def _post_teardown(self): + # Make sure you flush out the test modulestore after the end + # of the last test because otherwise on the next run + # cms/djangoapps/contentstore/__init__.py + # update_templates() will try to update the templates + # via upsert and it sometimes seems to be messing things up. + xmodule.modulestore.django._MODULESTORES = {} + xmodule.modulestore.django.modulestore().collection.drop() + super(CmsTestCase, self)._post_teardown() + +def parse_json(response): + """Parse response, which is assumed to be json""" + return json.loads(response.content) + +TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE) +TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') +TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data') + +@override_settings(MODULESTORE=TEST_DATA_MODULESTORE) +class NewContentStoreTest(CmsTestCase): + + def setUp(self): + # super(NewContentStoreTest, self).setUp() + uname = 'testuser' + email = 'test+courses@edx.org' + password = 'foo' + + # Create the use so we can log them in. + self.user = User.objects.create_user(uname, email, password) + + # Note that we do not actually need to do anything + # for registration if we directly mark them active. + self.user.is_active = True + # Staff has access to view all courses + self.user.is_staff = True + self.user.save() + + # user = UserFactory(username=uname, email=email, password=password, + # is_staff=True, is_active=True) + # user.is_authenticated= Mock(return_value=True) + + + self.client = Client() + self.client.login(username=uname, password=password) + + self.course_data = { + 'template': 'i4x://edx/templates/course/Empty', + 'org': 'MITx', + 'number': '999', + 'display_name': 'Robot Super Course', + } + + def tearDown(self): + # super(NewContentStoreTest, self).tearDown() + pass + + def test_create_course(self): + """Test new course creation - happy path""" + resp = self.client.post(reverse('create_new_course'), self.course_data) + self.assertEqual(resp.status_code, 200) + data = parse_json(resp) + self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') diff --git a/cms/envs/test.py b/cms/envs/test.py index d9a2597cbb..436aa2189e 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -10,7 +10,7 @@ sessions. Assumes structure: from .common import * import os from path import path - +from time import time # Nose Test Runner INSTALLED_APPS += ('django_nose',) @@ -39,11 +39,14 @@ STATICFILES_DIRS += [ if os.path.isdir(COMMON_TEST_DATA_ROOT / course_dir) ] +# Use the current seconds since epoch to differentiate +# the mongo collections on jenkins. +sec_since_epoch = '%s' % int(time()*100) modulestore_options = { 'default_class': 'xmodule.raw_module.RawDescriptor', 'host': 'localhost', 'db': 'test_xmodule', - 'collection': 'modulestore', + 'collection': 'modulestore_%s' % sec_since_epoch, 'fs_root': GITHUB_REPO_ROOT, 'render_template': 'mitxmako.shortcuts.render_to_string', } From b329b0ea42447cfb23a082a0860bfabdb46155af Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Sat, 2 Feb 2013 15:53:30 -0500 Subject: [PATCH 123/444] Remove toy dbs from CMS test settings.py file as they are not used. --- cms/envs/test.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/cms/envs/test.py b/cms/envs/test.py index 436aa2189e..c22965d748 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -75,17 +75,6 @@ DATABASES = { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ENV_ROOT / "db" / "cms.db", }, - - # The following are for testing purposes... - 'edX/toy/2012_Fall': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ENV_ROOT / "db" / "course1.db", - }, - - 'edx/full/6.002_Spring_2012': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ENV_ROOT / "db" / "course2.db", - } } LMS_BASE = "localhost:8000" From 2c5a7ccdf701aca7890b9b8cbf8fde1669912d1a Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Sat, 2 Feb 2013 16:39:46 -0500 Subject: [PATCH 124/444] Rearrange factories and clean up imports --- .../contentstore/tests/factories.py | 115 ----------------- cms/djangoapps/contentstore/tests/tests.py | 33 ++--- .../xmodule/modulestore/tests/factories.py | 117 ++++++++++++++++++ 3 files changed, 135 insertions(+), 130 deletions(-) create mode 100644 common/lib/xmodule/xmodule/modulestore/tests/factories.py diff --git a/cms/djangoapps/contentstore/tests/factories.py b/cms/djangoapps/contentstore/tests/factories.py index e9a535f372..f9c505d68f 100644 --- a/cms/djangoapps/contentstore/tests/factories.py +++ b/cms/djangoapps/contentstore/tests/factories.py @@ -1,13 +1,9 @@ from factory import Factory from datetime import datetime -from time import gmtime from uuid import uuid4 from student.models import (User, UserProfile, Registration, CourseEnrollmentAllowed) from django.contrib.auth.models import Group -from xmodule.modulestore import Location -from xmodule.modulestore.django import modulestore -from xmodule.timeparse import stringify_time class UserProfileFactory(Factory): FACTORY_FOR = UserProfile @@ -46,114 +42,3 @@ class CourseEnrollmentAllowedFactory(Factory): email = 'test@edx.org' course_id = 'edX/test/2012_Fall' - - -def XMODULE_COURSE_CREATION(class_to_create, **kwargs): - return XModuleCourseFactory._create(class_to_create, **kwargs) - -def XMODULE_ITEM_CREATION(class_to_create, **kwargs): - return XModuleItemFactory._create(class_to_create, **kwargs) - -class XModuleCourseFactory(Factory): - """ - Factory for XModule courses. - """ - - ABSTRACT_FACTORY = True - _creation_function = (XMODULE_COURSE_CREATION,) - - @classmethod - def _create(cls, target_class, *args, **kwargs): - - template = Location('i4x', 'edx', 'templates', 'course', 'Empty') - org = kwargs.get('org') - number = kwargs.get('number') - display_name = kwargs.get('display_name') - location = Location('i4x', org, number, - 'course', Location.clean(display_name)) - - store = modulestore('direct') - - # Write the data to the mongo datastore - new_course = store.clone_item(template, location) - - # This metadata code was copied from cms/djangoapps/contentstore/views.py - if display_name is not None: - new_course.metadata['display_name'] = display_name - - new_course.metadata['data_dir'] = uuid4().hex - new_course.metadata['start'] = stringify_time(gmtime()) - new_course.tabs = [{"type": "courseware"}, - {"type": "course_info", "name": "Course Info"}, - {"type": "discussion", "name": "Discussion"}, - {"type": "wiki", "name": "Wiki"}, - {"type": "progress", "name": "Progress"}] - - # Update the data in the mongo datastore - store.update_metadata(new_course.location.url(), new_course.own_metadata) - - return new_course - -class Course: - pass - -class CourseFactory(XModuleCourseFactory): - FACTORY_FOR = Course - - template = 'i4x://edx/templates/course/Empty' - org = 'MITx' - number = '999' - display_name = 'Robot Super Course' - -class XModuleItemFactory(Factory): - """ - Factory for XModule items. - """ - - ABSTRACT_FACTORY = True - _creation_function = (XMODULE_ITEM_CREATION,) - - @classmethod - def _create(cls, target_class, *args, **kwargs): - """ - kwargs must include parent_location, template. Can contain display_name - target_class is ignored - """ - - DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info'] - - parent_location = Location(kwargs.get('parent_location')) - template = Location(kwargs.get('template')) - display_name = kwargs.get('display_name') - - store = modulestore('direct') - - # This code was based off that in cms/djangoapps/contentstore/views.py - parent = store.get_item(parent_location) - dest_location = parent_location._replace(category=template.category, name=uuid4().hex) - - new_item = store.clone_item(template, dest_location) - - # TODO: This needs to be deleted when we have proper storage for static content - new_item.metadata['data_dir'] = parent.metadata['data_dir'] - - # replace the display name with an optional parameter passed in from the caller - if display_name is not None: - new_item.metadata['display_name'] = display_name - - store.update_metadata(new_item.location.url(), new_item.own_metadata) - - if new_item.location.category not in DETACHED_CATEGORIES: - store.update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()]) - - return new_item - -class Item: - pass - -class ItemFactory(XModuleItemFactory): - FACTORY_FOR = Item - - parent_location = 'i4x://MITx/999/course/Robot_Super_Course' - template = 'i4x://edx/templates/chapter/Empty' - display_name = 'Section One' \ No newline at end of file diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py index 085ecebff1..0727cb68dd 100644 --- a/cms/djangoapps/contentstore/tests/tests.py +++ b/cms/djangoapps/contentstore/tests/tests.py @@ -9,23 +9,26 @@ from path import path from tempfile import mkdtemp import json from fs.osfs import OSFS - +import copy from student.models import Registration from django.contrib.auth.models import User -import xmodule.modulestore.django -from xmodule.modulestore.xml_importer import import_from_xml -import copy -from factories import * +from cms.djangoapps.contentstore.utils import get_modulestore +from xmodule.modulestore import Location from xmodule.modulestore.store_utilities import clone_course from xmodule.modulestore.store_utilities import delete_course -from xmodule.modulestore.django import modulestore +from xmodule.modulestore.django import modulestore, _MODULESTORES from xmodule.contentstore.django import contentstore -from xmodule.course_module import CourseDescriptor +from xmodule.templates import update_templates from xmodule.modulestore.xml_exporter import export_to_xml -from cms.djangoapps.contentstore.utils import get_modulestore +from xmodule.modulestore.xml_importer import import_from_xml + from xmodule.capa_module import CapaDescriptor +from xmodule.course_module import CourseDescriptor +from xmodule.seq_module import SequenceDescriptor + +from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory def parse_json(response): """Parse response, which is assumed to be json""" @@ -217,9 +220,9 @@ class ContentStoreTest(TestCase): # (though it shouldn't), do this manually # from the bash shell to drop it: # $ mongo test_xmodule --eval "db.dropDatabase()" - xmodule.modulestore.django._MODULESTORES = {} - xmodule.modulestore.django.modulestore().collection.drop() - xmodule.templates.update_templates() + _MODULESTORES = {} + modulestore().collection.drop() + update_templates() self.client = Client() self.client.login(username=uname, password=password) @@ -237,8 +240,8 @@ class ContentStoreTest(TestCase): # cms/djangoapps/contentstore/__init__.py # update_templates() will try to update the templates # via upsert and it sometimes seems to be messing things up. - xmodule.modulestore.django._MODULESTORES = {} - xmodule.modulestore.django.modulestore().collection.drop() + _MODULESTORES = {} + modulestore().collection.drop() def test_create_course(self): """Test new course creation - happy path""" @@ -288,12 +291,12 @@ class ContentStoreTest(TestCase): def test_course_factory(self): course = CourseFactory.create() - self.assertIsInstance(course, xmodule.course_module.CourseDescriptor) + self.assertIsInstance(course, CourseDescriptor) def test_item_factory(self): course = CourseFactory.create() item = ItemFactory.create(parent_location=course.location) - self.assertIsInstance(item, xmodule.seq_module.SequenceDescriptor) + self.assertIsInstance(item, SequenceDescriptor) def test_course_index_view_with_course(self): """Test viewing the index page with an existing course""" diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py new file mode 100644 index 0000000000..987dbca09b --- /dev/null +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -0,0 +1,117 @@ +from factory import Factory +# from datetime import datetime +from time import gmtime +from uuid import uuid4 +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore +from xmodule.timeparse import stringify_time + +def XMODULE_COURSE_CREATION(class_to_create, **kwargs): + return XModuleCourseFactory._create(class_to_create, **kwargs) + +def XMODULE_ITEM_CREATION(class_to_create, **kwargs): + return XModuleItemFactory._create(class_to_create, **kwargs) + +class XModuleCourseFactory(Factory): + """ + Factory for XModule courses. + """ + + ABSTRACT_FACTORY = True + _creation_function = (XMODULE_COURSE_CREATION,) + + @classmethod + def _create(cls, target_class, *args, **kwargs): + + template = Location('i4x', 'edx', 'templates', 'course', 'Empty') + org = kwargs.get('org') + number = kwargs.get('number') + display_name = kwargs.get('display_name') + location = Location('i4x', org, number, + 'course', Location.clean(display_name)) + + store = modulestore('direct') + + # Write the data to the mongo datastore + new_course = store.clone_item(template, location) + + # This metadata code was copied from cms/djangoapps/contentstore/views.py + if display_name is not None: + new_course.metadata['display_name'] = display_name + + new_course.metadata['data_dir'] = uuid4().hex + new_course.metadata['start'] = stringify_time(gmtime()) + new_course.tabs = [{"type": "courseware"}, + {"type": "course_info", "name": "Course Info"}, + {"type": "discussion", "name": "Discussion"}, + {"type": "wiki", "name": "Wiki"}, + {"type": "progress", "name": "Progress"}] + + # Update the data in the mongo datastore + store.update_metadata(new_course.location.url(), new_course.own_metadata) + + return new_course + +class Course: + pass + +class CourseFactory(XModuleCourseFactory): + FACTORY_FOR = Course + + template = 'i4x://edx/templates/course/Empty' + org = 'MITx' + number = '999' + display_name = 'Robot Super Course' + +class XModuleItemFactory(Factory): + """ + Factory for XModule items. + """ + + ABSTRACT_FACTORY = True + _creation_function = (XMODULE_ITEM_CREATION,) + + @classmethod + def _create(cls, target_class, *args, **kwargs): + """ + kwargs must include parent_location, template. Can contain display_name + target_class is ignored + """ + + DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info'] + + parent_location = Location(kwargs.get('parent_location')) + template = Location(kwargs.get('template')) + display_name = kwargs.get('display_name') + + store = modulestore('direct') + + # This code was based off that in cms/djangoapps/contentstore/views.py + parent = store.get_item(parent_location) + dest_location = parent_location._replace(category=template.category, name=uuid4().hex) + + new_item = store.clone_item(template, dest_location) + + # TODO: This needs to be deleted when we have proper storage for static content + new_item.metadata['data_dir'] = parent.metadata['data_dir'] + + # replace the display name with an optional parameter passed in from the caller + if display_name is not None: + new_item.metadata['display_name'] = display_name + + store.update_metadata(new_item.location.url(), new_item.own_metadata) + + if new_item.location.category not in DETACHED_CATEGORIES: + store.update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()]) + + return new_item + +class Item: + pass + +class ItemFactory(XModuleItemFactory): + FACTORY_FOR = Item + + parent_location = 'i4x://MITx/999/course/Robot_Super_Course' + template = 'i4x://edx/templates/chapter/Empty' + display_name = 'Section One' \ No newline at end of file From 5a6b03920806fa83575238930cd9eb7bdc2a2abe Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Sat, 2 Feb 2013 22:29:40 -0500 Subject: [PATCH 125/444] Reorganize the CmsTestCase subclass --- cms/djangoapps/contentstore/tests/tests.py | 23 ++-------------- cms/djangoapps/contentstore/tests/utils.py | 32 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 21 deletions(-) create mode 100644 cms/djangoapps/contentstore/tests/utils.py diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py index 0727cb68dd..f41492a56e 100644 --- a/cms/djangoapps/contentstore/tests/tests.py +++ b/cms/djangoapps/contentstore/tests/tests.py @@ -29,6 +29,7 @@ from xmodule.course_module import CourseDescriptor from xmodule.seq_module import SequenceDescriptor from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory +from .utils import CmsTestCase def parse_json(response): """Parse response, which is assumed to be json""" @@ -196,7 +197,7 @@ TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data' TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data') @override_settings(MODULESTORE=TEST_DATA_MODULESTORE) -class ContentStoreTest(TestCase): +class ContentStoreTest(CmsTestCase): def setUp(self): uname = 'testuser' @@ -213,17 +214,6 @@ class ContentStoreTest(TestCase): self.user.is_staff = True self.user.save() - # Flush and initialize the module store - # It needs the templates because it creates new records - # by cloning from the template. - # Note that if your test module gets in some weird state - # (though it shouldn't), do this manually - # from the bash shell to drop it: - # $ mongo test_xmodule --eval "db.dropDatabase()" - _MODULESTORES = {} - modulestore().collection.drop() - update_templates() - self.client = Client() self.client.login(username=uname, password=password) @@ -234,15 +224,6 @@ class ContentStoreTest(TestCase): 'display_name': 'Robot Super Course', } - def tearDown(self): - # Make sure you flush out the test modulestore after the end - # of the last test because otherwise on the next run - # cms/djangoapps/contentstore/__init__.py - # update_templates() will try to update the templates - # via upsert and it sometimes seems to be messing things up. - _MODULESTORES = {} - modulestore().collection.drop() - def test_create_course(self): """Test new course creation - happy path""" resp = self.client.post(reverse('create_new_course'), self.course_data) diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py new file mode 100644 index 0000000000..c800ee936c --- /dev/null +++ b/cms/djangoapps/contentstore/tests/utils.py @@ -0,0 +1,32 @@ +from django.test import TestCase +from xmodule.modulestore.django import modulestore, _MODULESTORES +from xmodule.templates import update_templates + +# Subclass TestCase and use to initialize the contentstore +class CmsTestCase(TestCase): + """ Subclass for any test case that uses the mongodb + module store. This clears it out before running the TestCase + and reinitilizes it with the templates afterwards. """ + + def _pre_setup(self): + super(CmsTestCase, self)._pre_setup() + # Flush and initialize the module store + # It needs the templates because it creates new records + # by cloning from the template. + # Note that if your test module gets in some weird state + # (though it shouldn't), do this manually + # from the bash shell to drop it: + # $ mongo test_xmodule --eval "db.dropDatabase()" + _MODULESTORES = {} + modulestore().collection.drop() + update_templates() + + def _post_teardown(self): + # Make sure you flush out the test modulestore after the end + # of the last test because otherwise on the next run + # cms/djangoapps/contentstore/__init__.py + # update_templates() will try to update the templates + # via upsert and it sometimes seems to be messing things up. + _MODULESTORES = {} + modulestore().collection.drop() + super(CmsTestCase, self)._post_teardown() \ No newline at end of file From 3324615270cee928055148a64b579dbb06e43129 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Mon, 4 Feb 2013 08:26:21 -0500 Subject: [PATCH 126/444] Reorganize test cases --- .../contentstore/tests/test_contentstore.py | 334 +++++++++++++++--- cms/djangoapps/contentstore/tests/tests.py | 333 ----------------- 2 files changed, 293 insertions(+), 374 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index cc37d201a1..a417418410 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -13,46 +13,16 @@ from mock import Mock import xmodule.modulestore.django from factories import * - -# Subclass TestCase and use to initialize the contentstore -class CmsTestCase(TestCase): - - def _pre_setup(self): - super(CmsTestCase, self)._pre_setup() - # Flush and initialize the module store - # It needs the templates because it creates new records - # by cloning from the template. - # Note that if your test module gets in some weird state - # (though it shouldn't), do this manually - # from the bash shell to drop it: - # $ mongo test_xmodule --eval "db.dropDatabase()" - xmodule.modulestore.django._MODULESTORES = {} - xmodule.modulestore.django.modulestore().collection.drop() - xmodule.templates.update_templates() - - def _post_teardown(self): - # Make sure you flush out the test modulestore after the end - # of the last test because otherwise on the next run - # cms/djangoapps/contentstore/__init__.py - # update_templates() will try to update the templates - # via upsert and it sometimes seems to be messing things up. - xmodule.modulestore.django._MODULESTORES = {} - xmodule.modulestore.django.modulestore().collection.drop() - super(CmsTestCase, self)._post_teardown() - -def parse_json(response): - """Parse response, which is assumed to be json""" - return json.loads(response.content) +from utils import CmsTestCase TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE) TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data') @override_settings(MODULESTORE=TEST_DATA_MODULESTORE) -class NewContentStoreTest(CmsTestCase): +class ContentStoreTest(CmsTestCase): def setUp(self): - # super(NewContentStoreTest, self).setUp() uname = 'testuser' email = 'test+courses@edx.org' password = 'foo' @@ -67,11 +37,6 @@ class NewContentStoreTest(CmsTestCase): self.user.is_staff = True self.user.save() - # user = UserFactory(username=uname, email=email, password=password, - # is_staff=True, is_active=True) - # user.is_authenticated= Mock(return_value=True) - - self.client = Client() self.client.login(username=uname, password=password) @@ -82,13 +47,300 @@ class NewContentStoreTest(CmsTestCase): 'display_name': 'Robot Super Course', } - def tearDown(self): - # super(NewContentStoreTest, self).tearDown() - pass - def test_create_course(self): """Test new course creation - happy path""" resp = self.client.post(reverse('create_new_course'), self.course_data) self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') + + def test_create_course_duplicate_course(self): + """Test new course creation - error path""" + resp = self.client.post(reverse('create_new_course'), self.course_data) + resp = self.client.post(reverse('create_new_course'), self.course_data) + data = parse_json(resp) + self.assertEqual(resp.status_code, 200) + self.assertEqual(data['ErrMsg'], 'There is already a course defined with this name.') + + def test_create_course_duplicate_number(self): + """Test new course creation - error path""" + resp = self.client.post(reverse('create_new_course'), self.course_data) + self.course_data['display_name'] = 'Robot Super Course Two' + + resp = self.client.post(reverse('create_new_course'), self.course_data) + data = parse_json(resp) + + self.assertEqual(resp.status_code, 200) + self.assertEqual(data['ErrMsg'], + 'There is already a course defined with the same organization and course number.') + + def test_create_course_with_bad_organization(self): + """Test new course creation - error path for bad organization name""" + self.course_data['org'] = 'University of California, Berkeley' + resp = self.client.post(reverse('create_new_course'), self.course_data) + data = parse_json(resp) + + self.assertEqual(resp.status_code, 200) + self.assertEqual(data['ErrMsg'], + "Unable to create course 'Robot Super Course'.\n\nInvalid characters in 'University of California, Berkeley'.") + + def test_course_index_view_with_no_courses(self): + """Test viewing the index page with no courses""" + # Create a course so there is something to view + resp = self.client.get(reverse('index')) + self.assertContains(resp, + '

    My Courses

    ', + status_code=200, + html=True) + + def test_course_factory(self): + course = CourseFactory.create() + self.assertIsInstance(course, CourseDescriptor) + + def test_item_factory(self): + course = CourseFactory.create() + item = ItemFactory.create(parent_location=course.location) + self.assertIsInstance(item, SequenceDescriptor) + + def test_course_index_view_with_course(self): + """Test viewing the index page with an existing course""" + CourseFactory.create(display_name='Robot Super Educational Course') + resp = self.client.get(reverse('index')) + self.assertContains(resp, + 'Robot Super Educational Course', + status_code=200, + html=True) + + def test_course_overview_view_with_course(self): + """Test viewing the course overview page with an existing course""" + CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') + + data = { + 'org': 'MITx', + 'course': '999', + 'name': Location.clean('Robot Super Course'), + } + + resp = self.client.get(reverse('course_index', kwargs=data)) + self.assertContains(resp, + 'Robot Super Course', + status_code=200, + html=True) + + def test_clone_item(self): + """Test cloning an item. E.g. creating a new section""" + CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') + + section_data = { + 'parent_location' : 'i4x://MITx/999/course/Robot_Super_Course', + 'template' : 'i4x://edx/templates/chapter/Empty', + 'display_name': 'Section One', + } + + resp = self.client.post(reverse('clone_item'), section_data) + + self.assertEqual(resp.status_code, 200) + data = parse_json(resp) + self.assertRegexpMatches(data['id'], + '^i4x:\/\/MITx\/999\/chapter\/([0-9]|[a-f]){32}$') + + def check_edit_unit(self, test_course_name): + import_from_xml(modulestore(), 'common/test/data/', [test_course_name]) + + for descriptor in modulestore().get_items(Location(None, None, 'vertical', None, None)): + print "Checking ", descriptor.location.url() + print descriptor.__class__, descriptor.location + resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()})) + self.assertEqual(resp.status_code, 200) + + def test_edit_unit_toy(self): + self.check_edit_unit('toy') + + def test_edit_unit_full(self): + self.check_edit_unit('full') + + def test_static_tab_reordering(self): + import_from_xml(modulestore(), 'common/test/data/', ['full']) + + ms = modulestore('direct') + course = ms.get_item(Location(['i4x','edX','full','course','6.002_Spring_2012', None])) + + # reverse the ordering + reverse_tabs = [] + for tab in course.tabs: + if tab['type'] == 'static_tab': + reverse_tabs.insert(0, 'i4x://edX/full/static_tab/{0}'.format(tab['url_slug'])) + + resp = self.client.post(reverse('reorder_static_tabs'), json.dumps({'tabs':reverse_tabs}), "application/json") + + course = ms.get_item(Location(['i4x','edX','full','course','6.002_Spring_2012', None])) + + # compare to make sure that the tabs information is in the expected order after the server call + course_tabs = [] + for tab in course.tabs: + if tab['type'] == 'static_tab': + course_tabs.append('i4x://edX/full/static_tab/{0}'.format(tab['url_slug'])) + + self.assertEqual(reverse_tabs, course_tabs) + + + + + def test_about_overrides(self): + ''' + This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html + while there is a base definition in /about/effort.html + ''' + import_from_xml(modulestore(), 'common/test/data/', ['full']) + ms = modulestore('direct') + effort = ms.get_item(Location(['i4x','edX','full','about','effort', None])) + self.assertEqual(effort.definition['data'],'6 hours') + + # this one should be in a non-override folder + effort = ms.get_item(Location(['i4x','edX','full','about','end_date', None])) + self.assertEqual(effort.definition['data'],'TBD') + + def test_remove_hide_progress_tab(self): + import_from_xml(modulestore(), 'common/test/data/', ['full']) + + ms = modulestore('direct') + cs = contentstore() + + source_location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') + course = ms.get_item(source_location) + self.assertNotIn('hide_progress_tab', course.metadata) + + + def test_clone_course(self): + import_from_xml(modulestore(), 'common/test/data/', ['full']) + + resp = self.client.post(reverse('create_new_course'), self.course_data) + self.assertEqual(resp.status_code, 200) + data = parse_json(resp) + self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') + + ms = modulestore('direct') + cs = contentstore() + + source_location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') + dest_location = CourseDescriptor.id_to_location('MITx/999/Robot_Super_Course') + + clone_course(ms, cs, source_location, dest_location) + + # now loop through all the units in the course and verify that the clone can render them, which + # means the objects are at least present + items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None])) + self.assertGreater(len(items), 0) + clone_items = ms.get_items(Location(['i4x', 'MITx','999','vertical', None])) + self.assertGreater(len(clone_items), 0) + for descriptor in items: + new_loc = descriptor.location._replace(org = 'MITx', course='999') + print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url()) + resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()})) + self.assertEqual(resp.status_code, 200) + + def test_delete_course(self): + import_from_xml(modulestore(), 'common/test/data/', ['full']) + + ms = modulestore('direct') + cs = contentstore() + + location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') + + delete_course(ms, cs, location) + + items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None])) + self.assertEqual(len(items), 0) + + def verify_content_existence(self, modulestore, root_dir, location, dirname, category_name, filename_suffix=''): + fs = OSFS(root_dir / 'test_export') + self.assertTrue(fs.exists(dirname)) + + query_loc = Location('i4x', location.org, location.course, category_name, None) + items = modulestore.get_items(query_loc) + + for item in items: + fs = OSFS(root_dir / ('test_export/' + dirname)) + self.assertTrue(fs.exists(item.location.name + filename_suffix)) + + def test_export_course(self): + ms = modulestore('direct') + cs = contentstore() + + import_from_xml(ms, 'common/test/data/', ['full']) + location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') + + root_dir = path(mkdtemp()) + + print 'Exporting to tempdir = {0}'.format(root_dir) + + # export out to a tempdir + export_to_xml(ms, cs, location, root_dir, 'test_export') + + # check for static tabs + self.verify_content_existence(ms, root_dir, location, 'tabs', 'static_tab', '.html') + + # check for custom_tags + self.verify_content_existence(ms, root_dir, location, 'info', 'course_info', '.html') + + # check for custom_tags + self.verify_content_existence(ms, root_dir, location, 'custom_tags', 'custom_tag_template') + + + # remove old course + delete_course(ms, cs, location) + + # reimport + import_from_xml(ms, root_dir, ['test_export']) + + items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None])) + self.assertGreater(len(items), 0) + for descriptor in items: + print "Checking {0}....".format(descriptor.location.url()) + resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()})) + self.assertEqual(resp.status_code, 200) + + shutil.rmtree(root_dir) + + + def test_course_handouts_rewrites(self): + ms = modulestore('direct') + cs = contentstore() + + # import a test course + import_from_xml(ms, 'common/test/data/', ['full']) + + handout_location= Location(['i4x', 'edX', 'full', 'course_info', 'handouts']) + + # get module info + resp = self.client.get(reverse('module_info', kwargs={'module_location': handout_location})) + + # make sure we got a successful response + self.assertEqual(resp.status_code, 200) + + # check that /static/ has been converted to the full path + # note, we know the link it should be because that's what in the 'full' course in the test data + self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf') + + + def test_capa_module(self): + """Test that a problem treats markdown specially.""" + CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') + + problem_data = { + 'parent_location' : 'i4x://MITx/999/course/Robot_Super_Course', + 'template' : 'i4x://edx/templates/problem/Empty' + } + + resp = self.client.post(reverse('clone_item'), problem_data) + + self.assertEqual(resp.status_code, 200) + payload = parse_json(resp) + problem_loc = payload['id'] + problem = get_modulestore(problem_loc).get_item(problem_loc) + # should be a CapaDescriptor + self.assertIsInstance(problem, CapaDescriptor, "New problem is not a CapaDescriptor") + context = problem.get_context() + self.assertIn('markdown', context, "markdown is missing from context") + self.assertIn('markdown', problem.metadata, "markdown is missing from metadata") + self.assertNotIn('markdown', problem.editable_metadata_fields, "Markdown slipped into the editable metadata fields") diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py index f41492a56e..8c8ee07264 100644 --- a/cms/djangoapps/contentstore/tests/tests.py +++ b/cms/djangoapps/contentstore/tests/tests.py @@ -191,336 +191,3 @@ class AuthTestCase(ContentStoreTestCase): self.assertEqual(resp.status_code, 302) # Logged in should work. - -TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE) -TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') -TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data') - -@override_settings(MODULESTORE=TEST_DATA_MODULESTORE) -class ContentStoreTest(CmsTestCase): - - def setUp(self): - uname = 'testuser' - email = 'test+courses@edx.org' - password = 'foo' - - # Create the use so we can log them in. - self.user = User.objects.create_user(uname, email, password) - - # Note that we do not actually need to do anything - # for registration if we directly mark them active. - self.user.is_active = True - # Staff has access to view all courses - self.user.is_staff = True - self.user.save() - - self.client = Client() - self.client.login(username=uname, password=password) - - self.course_data = { - 'template': 'i4x://edx/templates/course/Empty', - 'org': 'MITx', - 'number': '999', - 'display_name': 'Robot Super Course', - } - - def test_create_course(self): - """Test new course creation - happy path""" - resp = self.client.post(reverse('create_new_course'), self.course_data) - self.assertEqual(resp.status_code, 200) - data = parse_json(resp) - self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') - - def test_create_course_duplicate_course(self): - """Test new course creation - error path""" - resp = self.client.post(reverse('create_new_course'), self.course_data) - resp = self.client.post(reverse('create_new_course'), self.course_data) - data = parse_json(resp) - self.assertEqual(resp.status_code, 200) - self.assertEqual(data['ErrMsg'], 'There is already a course defined with this name.') - - def test_create_course_duplicate_number(self): - """Test new course creation - error path""" - resp = self.client.post(reverse('create_new_course'), self.course_data) - self.course_data['display_name'] = 'Robot Super Course Two' - - resp = self.client.post(reverse('create_new_course'), self.course_data) - data = parse_json(resp) - - self.assertEqual(resp.status_code, 200) - self.assertEqual(data['ErrMsg'], - 'There is already a course defined with the same organization and course number.') - - def test_create_course_with_bad_organization(self): - """Test new course creation - error path for bad organization name""" - self.course_data['org'] = 'University of California, Berkeley' - resp = self.client.post(reverse('create_new_course'), self.course_data) - data = parse_json(resp) - - self.assertEqual(resp.status_code, 200) - self.assertEqual(data['ErrMsg'], - "Unable to create course 'Robot Super Course'.\n\nInvalid characters in 'University of California, Berkeley'.") - - def test_course_index_view_with_no_courses(self): - """Test viewing the index page with no courses""" - # Create a course so there is something to view - resp = self.client.get(reverse('index')) - self.assertContains(resp, - '

    My Courses

    ', - status_code=200, - html=True) - - def test_course_factory(self): - course = CourseFactory.create() - self.assertIsInstance(course, CourseDescriptor) - - def test_item_factory(self): - course = CourseFactory.create() - item = ItemFactory.create(parent_location=course.location) - self.assertIsInstance(item, SequenceDescriptor) - - def test_course_index_view_with_course(self): - """Test viewing the index page with an existing course""" - CourseFactory.create(display_name='Robot Super Educational Course') - resp = self.client.get(reverse('index')) - self.assertContains(resp, - 'Robot Super Educational Course', - status_code=200, - html=True) - - def test_course_overview_view_with_course(self): - """Test viewing the course overview page with an existing course""" - CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') - - data = { - 'org': 'MITx', - 'course': '999', - 'name': Location.clean('Robot Super Course'), - } - - resp = self.client.get(reverse('course_index', kwargs=data)) - self.assertContains(resp, - 'Robot Super Course', - status_code=200, - html=True) - - def test_clone_item(self): - """Test cloning an item. E.g. creating a new section""" - CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') - - section_data = { - 'parent_location' : 'i4x://MITx/999/course/Robot_Super_Course', - 'template' : 'i4x://edx/templates/chapter/Empty', - 'display_name': 'Section One', - } - - resp = self.client.post(reverse('clone_item'), section_data) - - self.assertEqual(resp.status_code, 200) - data = parse_json(resp) - self.assertRegexpMatches(data['id'], - '^i4x:\/\/MITx\/999\/chapter\/([0-9]|[a-f]){32}$') - - def check_edit_unit(self, test_course_name): - import_from_xml(modulestore(), 'common/test/data/', [test_course_name]) - - for descriptor in modulestore().get_items(Location(None, None, 'vertical', None, None)): - print "Checking ", descriptor.location.url() - print descriptor.__class__, descriptor.location - resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()})) - self.assertEqual(resp.status_code, 200) - - def test_edit_unit_toy(self): - self.check_edit_unit('toy') - - def test_edit_unit_full(self): - self.check_edit_unit('full') - - def test_static_tab_reordering(self): - import_from_xml(modulestore(), 'common/test/data/', ['full']) - - ms = modulestore('direct') - course = ms.get_item(Location(['i4x','edX','full','course','6.002_Spring_2012', None])) - - # reverse the ordering - reverse_tabs = [] - for tab in course.tabs: - if tab['type'] == 'static_tab': - reverse_tabs.insert(0, 'i4x://edX/full/static_tab/{0}'.format(tab['url_slug'])) - - resp = self.client.post(reverse('reorder_static_tabs'), json.dumps({'tabs':reverse_tabs}), "application/json") - - course = ms.get_item(Location(['i4x','edX','full','course','6.002_Spring_2012', None])) - - # compare to make sure that the tabs information is in the expected order after the server call - course_tabs = [] - for tab in course.tabs: - if tab['type'] == 'static_tab': - course_tabs.append('i4x://edX/full/static_tab/{0}'.format(tab['url_slug'])) - - self.assertEqual(reverse_tabs, course_tabs) - - - - - def test_about_overrides(self): - ''' - This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html - while there is a base definition in /about/effort.html - ''' - import_from_xml(modulestore(), 'common/test/data/', ['full']) - ms = modulestore('direct') - effort = ms.get_item(Location(['i4x','edX','full','about','effort', None])) - self.assertEqual(effort.definition['data'],'6 hours') - - # this one should be in a non-override folder - effort = ms.get_item(Location(['i4x','edX','full','about','end_date', None])) - self.assertEqual(effort.definition['data'],'TBD') - - def test_remove_hide_progress_tab(self): - import_from_xml(modulestore(), 'common/test/data/', ['full']) - - ms = modulestore('direct') - cs = contentstore() - - source_location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') - course = ms.get_item(source_location) - self.assertNotIn('hide_progress_tab', course.metadata) - - - def test_clone_course(self): - import_from_xml(modulestore(), 'common/test/data/', ['full']) - - resp = self.client.post(reverse('create_new_course'), self.course_data) - self.assertEqual(resp.status_code, 200) - data = parse_json(resp) - self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') - - ms = modulestore('direct') - cs = contentstore() - - source_location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') - dest_location = CourseDescriptor.id_to_location('MITx/999/Robot_Super_Course') - - clone_course(ms, cs, source_location, dest_location) - - # now loop through all the units in the course and verify that the clone can render them, which - # means the objects are at least present - items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None])) - self.assertGreater(len(items), 0) - clone_items = ms.get_items(Location(['i4x', 'MITx','999','vertical', None])) - self.assertGreater(len(clone_items), 0) - for descriptor in items: - new_loc = descriptor.location._replace(org = 'MITx', course='999') - print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url()) - resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()})) - self.assertEqual(resp.status_code, 200) - - def test_delete_course(self): - import_from_xml(modulestore(), 'common/test/data/', ['full']) - - ms = modulestore('direct') - cs = contentstore() - - location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') - - delete_course(ms, cs, location) - - items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None])) - self.assertEqual(len(items), 0) - - def verify_content_existence(self, modulestore, root_dir, location, dirname, category_name, filename_suffix=''): - fs = OSFS(root_dir / 'test_export') - self.assertTrue(fs.exists(dirname)) - - query_loc = Location('i4x', location.org, location.course, category_name, None) - items = modulestore.get_items(query_loc) - - for item in items: - fs = OSFS(root_dir / ('test_export/' + dirname)) - self.assertTrue(fs.exists(item.location.name + filename_suffix)) - - def test_export_course(self): - ms = modulestore('direct') - cs = contentstore() - - import_from_xml(ms, 'common/test/data/', ['full']) - location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') - - root_dir = path(mkdtemp()) - - print 'Exporting to tempdir = {0}'.format(root_dir) - - # export out to a tempdir - export_to_xml(ms, cs, location, root_dir, 'test_export') - - # check for static tabs - self.verify_content_existence(ms, root_dir, location, 'tabs', 'static_tab', '.html') - - # check for custom_tags - self.verify_content_existence(ms, root_dir, location, 'info', 'course_info', '.html') - - # check for custom_tags - self.verify_content_existence(ms, root_dir, location, 'custom_tags', 'custom_tag_template') - - - # remove old course - delete_course(ms, cs, location) - - # reimport - import_from_xml(ms, root_dir, ['test_export']) - - items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None])) - self.assertGreater(len(items), 0) - for descriptor in items: - print "Checking {0}....".format(descriptor.location.url()) - resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()})) - self.assertEqual(resp.status_code, 200) - - shutil.rmtree(root_dir) - - - def test_course_handouts_rewrites(self): - ms = modulestore('direct') - cs = contentstore() - - # import a test course - import_from_xml(ms, 'common/test/data/', ['full']) - - handout_location= Location(['i4x', 'edX', 'full', 'course_info', 'handouts']) - - # get module info - resp = self.client.get(reverse('module_info', kwargs={'module_location': handout_location})) - - # make sure we got a successful response - self.assertEqual(resp.status_code, 200) - - # check that /static/ has been converted to the full path - # note, we know the link it should be because that's what in the 'full' course in the test data - self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf') - - def test_missing_static_content(self): - resp = self.client.get("/c4x/asd/asd/asd/asd") - self.assertEqual(resp.status_code, 404) - - def test_capa_module(self): - """Test that a problem treats markdown specially.""" - CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') - - problem_data = { - 'parent_location' : 'i4x://MITx/999/course/Robot_Super_Course', - 'template' : 'i4x://edx/templates/problem/Empty' - } - - resp = self.client.post(reverse('clone_item'), problem_data) - - self.assertEqual(resp.status_code, 200) - payload = parse_json(resp) - problem_loc = payload['id'] - problem = get_modulestore(problem_loc).get_item(problem_loc) - # should be a CapaDescriptor - self.assertIsInstance(problem, CapaDescriptor, "New problem is not a CapaDescriptor") - context = problem.get_context() - self.assertIn('markdown', context, "markdown is missing from context") - self.assertIn('markdown', problem.metadata, "markdown is missing from metadata") - self.assertNotIn('markdown', problem.editable_metadata_fields, "Markdown slipped into the editable metadata fields") \ No newline at end of file From fde6d1ba1ca9c1f6005bacf032f3e0fd7fac8f89 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Mon, 4 Feb 2013 10:57:20 -0500 Subject: [PATCH 127/444] Refactor tests for cms contentstore. --- .../contentstore/tests/test_contentstore.py | 284 +++++++++++------- .../tests/test_course_settings.py | 71 ++--- cms/djangoapps/contentstore/tests/tests.py | 19 +- cms/djangoapps/contentstore/tests/utils.py | 40 ++- cms/djangoapps/contentstore/views.py | 12 +- .../xmodule/modulestore/tests/factories.py | 7 +- 6 files changed, 242 insertions(+), 191 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index a417418410..ce7d9e757c 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -1,27 +1,46 @@ import json import shutil -from django.test import TestCase from django.test.client import Client from override_settings import override_settings from django.conf import settings from django.core.urlresolvers import reverse from path import path +from tempfile import mkdtemp import json from fs.osfs import OSFS import copy from mock import Mock -import xmodule.modulestore.django -from factories import * -from utils import CmsTestCase +from student.models import Registration +from django.contrib.auth.models import User +from cms.djangoapps.contentstore.utils import get_modulestore + +from utils import ModuleStoreTestCase, parse_json +from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory + +from xmodule.modulestore import Location +from xmodule.modulestore.store_utilities import clone_course +from xmodule.modulestore.store_utilities import delete_course +from xmodule.modulestore.django import modulestore, _MODULESTORES +from xmodule.contentstore.django import contentstore +from xmodule.templates import update_templates +from xmodule.modulestore.xml_exporter import export_to_xml +from xmodule.modulestore.xml_importer import import_from_xml + +from xmodule.capa_module import CapaDescriptor +from xmodule.course_module import CourseDescriptor +from xmodule.seq_module import SequenceDescriptor TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE) TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data') @override_settings(MODULESTORE=TEST_DATA_MODULESTORE) -class ContentStoreTest(CmsTestCase): - +class ContentStoreToyCourseTest(ModuleStoreTestCase): + """ + Tests that rely on the toy courses. + TODO: refactor using CourseFactory so they do not. + """ def setUp(self): uname = 'testuser' email = 'test+courses@edx.org' @@ -40,109 +59,6 @@ class ContentStoreTest(CmsTestCase): self.client = Client() self.client.login(username=uname, password=password) - self.course_data = { - 'template': 'i4x://edx/templates/course/Empty', - 'org': 'MITx', - 'number': '999', - 'display_name': 'Robot Super Course', - } - - def test_create_course(self): - """Test new course creation - happy path""" - resp = self.client.post(reverse('create_new_course'), self.course_data) - self.assertEqual(resp.status_code, 200) - data = parse_json(resp) - self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') - - def test_create_course_duplicate_course(self): - """Test new course creation - error path""" - resp = self.client.post(reverse('create_new_course'), self.course_data) - resp = self.client.post(reverse('create_new_course'), self.course_data) - data = parse_json(resp) - self.assertEqual(resp.status_code, 200) - self.assertEqual(data['ErrMsg'], 'There is already a course defined with this name.') - - def test_create_course_duplicate_number(self): - """Test new course creation - error path""" - resp = self.client.post(reverse('create_new_course'), self.course_data) - self.course_data['display_name'] = 'Robot Super Course Two' - - resp = self.client.post(reverse('create_new_course'), self.course_data) - data = parse_json(resp) - - self.assertEqual(resp.status_code, 200) - self.assertEqual(data['ErrMsg'], - 'There is already a course defined with the same organization and course number.') - - def test_create_course_with_bad_organization(self): - """Test new course creation - error path for bad organization name""" - self.course_data['org'] = 'University of California, Berkeley' - resp = self.client.post(reverse('create_new_course'), self.course_data) - data = parse_json(resp) - - self.assertEqual(resp.status_code, 200) - self.assertEqual(data['ErrMsg'], - "Unable to create course 'Robot Super Course'.\n\nInvalid characters in 'University of California, Berkeley'.") - - def test_course_index_view_with_no_courses(self): - """Test viewing the index page with no courses""" - # Create a course so there is something to view - resp = self.client.get(reverse('index')) - self.assertContains(resp, - '

    My Courses

    ', - status_code=200, - html=True) - - def test_course_factory(self): - course = CourseFactory.create() - self.assertIsInstance(course, CourseDescriptor) - - def test_item_factory(self): - course = CourseFactory.create() - item = ItemFactory.create(parent_location=course.location) - self.assertIsInstance(item, SequenceDescriptor) - - def test_course_index_view_with_course(self): - """Test viewing the index page with an existing course""" - CourseFactory.create(display_name='Robot Super Educational Course') - resp = self.client.get(reverse('index')) - self.assertContains(resp, - 'Robot Super Educational Course', - status_code=200, - html=True) - - def test_course_overview_view_with_course(self): - """Test viewing the course overview page with an existing course""" - CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') - - data = { - 'org': 'MITx', - 'course': '999', - 'name': Location.clean('Robot Super Course'), - } - - resp = self.client.get(reverse('course_index', kwargs=data)) - self.assertContains(resp, - 'Robot Super Course', - status_code=200, - html=True) - - def test_clone_item(self): - """Test cloning an item. E.g. creating a new section""" - CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') - - section_data = { - 'parent_location' : 'i4x://MITx/999/course/Robot_Super_Course', - 'template' : 'i4x://edx/templates/chapter/Empty', - 'display_name': 'Section One', - } - - resp = self.client.post(reverse('clone_item'), section_data) - - self.assertEqual(resp.status_code, 200) - data = parse_json(resp) - self.assertRegexpMatches(data['id'], - '^i4x:\/\/MITx\/999\/chapter\/([0-9]|[a-f]){32}$') def check_edit_unit(self, test_course_name): import_from_xml(modulestore(), 'common/test/data/', [test_course_name]) @@ -183,9 +99,6 @@ class ContentStoreTest(CmsTestCase): self.assertEqual(reverse_tabs, course_tabs) - - - def test_about_overrides(self): ''' This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html @@ -210,11 +123,18 @@ class ContentStoreTest(CmsTestCase): course = ms.get_item(source_location) self.assertNotIn('hide_progress_tab', course.metadata) - def test_clone_course(self): + + course_data = { + 'template': 'i4x://edx/templates/course/Empty', + 'org': 'MITx', + 'number': '999', + 'display_name': 'Robot Super Course', + } + import_from_xml(modulestore(), 'common/test/data/', ['full']) - resp = self.client.post(reverse('create_new_course'), self.course_data) + resp = self.client.post(reverse('create_new_course'), course_data) self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') @@ -302,7 +222,6 @@ class ContentStoreTest(CmsTestCase): shutil.rmtree(root_dir) - def test_course_handouts_rewrites(self): ms = modulestore('direct') cs = contentstore() @@ -323,6 +242,141 @@ class ContentStoreTest(CmsTestCase): self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf') +class ContentStoreTest(ModuleStoreTestCase): + """ + Tests for the CMS ContentStore application. + """ + def setUp(self): + """ + These tests need a user in the DB so that the django Test Client + can log them in. + They inherit from the ModuleStoreTestCase class so that the mongodb collection + will be cleared out before each test case execution and deleted + afterwards. + """ + uname = 'testuser' + email = 'test+courses@edx.org' + password = 'foo' + + # Create the use so we can log them in. + self.user = User.objects.create_user(uname, email, password) + + # Note that we do not actually need to do anything + # for registration if we directly mark them active. + self.user.is_active = True + # Staff has access to view all courses + self.user.is_staff = True + self.user.save() + + self.client = Client() + self.client.login(username=uname, password=password) + + self.course_data = { + 'template': 'i4x://edx/templates/course/Empty', + 'org': 'MITx', + 'number': '999', + 'display_name': 'Robot Super Course', + } + + def test_create_course(self): + """Test new course creation - happy path""" + resp = self.client.post(reverse('create_new_course'), self.course_data) + self.assertEqual(resp.status_code, 200) + data = parse_json(resp) + self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') + + def test_create_course_duplicate_course(self): + """Test new course creation - error path""" + resp = self.client.post(reverse('create_new_course'), self.course_data) + resp = self.client.post(reverse('create_new_course'), self.course_data) + data = parse_json(resp) + self.assertEqual(resp.status_code, 200) + self.assertEqual(data['ErrMsg'], 'There is already a course defined with this name.') + + def test_create_course_duplicate_number(self): + """Test new course creation - error path""" + resp = self.client.post(reverse('create_new_course'), self.course_data) + self.course_data['display_name'] = 'Robot Super Course Two' + + resp = self.client.post(reverse('create_new_course'), self.course_data) + data = parse_json(resp) + + self.assertEqual(resp.status_code, 200) + self.assertEqual(data['ErrMsg'], + 'There is already a course defined with the same organization and course number.') + + def test_create_course_with_bad_organization(self): + """Test new course creation - error path for bad organization name""" + self.course_data['org'] = 'University of California, Berkeley' + resp = self.client.post(reverse('create_new_course'), self.course_data) + data = parse_json(resp) + + self.assertEqual(resp.status_code, 200) + self.assertEqual(data['ErrMsg'], + "Unable to create course 'Robot Super Course'.\n\nInvalid characters in 'University of California, Berkeley'.") + + def test_course_index_view_with_no_courses(self): + """Test viewing the index page with no courses""" + # Create a course so there is something to view + resp = self.client.get(reverse('index')) + self.assertContains(resp, + '

    My Courses

    ', + status_code=200, + html=True) + + def test_course_factory(self): + """Test that the course factory works correctly.""" + course = CourseFactory.create() + self.assertIsInstance(course, CourseDescriptor) + + def test_item_factory(self): + """Test that the item factory works correctly.""" + course = CourseFactory.create() + item = ItemFactory.create(parent_location=course.location) + self.assertIsInstance(item, SequenceDescriptor) + + def test_course_index_view_with_course(self): + """Test viewing the index page with an existing course""" + CourseFactory.create(display_name='Robot Super Educational Course') + resp = self.client.get(reverse('index')) + self.assertContains(resp, + 'Robot Super Educational Course', + status_code=200, + html=True) + + def test_course_overview_view_with_course(self): + """Test viewing the course overview page with an existing course""" + CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') + + data = { + 'org': 'MITx', + 'course': '999', + 'name': Location.clean('Robot Super Course'), + } + + resp = self.client.get(reverse('course_index', kwargs=data)) + self.assertContains(resp, + 'Robot Super Course', + status_code=200, + html=True) + + def test_clone_item(self): + """Test cloning an item. E.g. creating a new section""" + CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') + + section_data = { + 'parent_location' : 'i4x://MITx/999/course/Robot_Super_Course', + 'template' : 'i4x://edx/templates/chapter/Empty', + 'display_name': 'Section One', + } + + resp = self.client.post(reverse('clone_item'), section_data) + + self.assertEqual(resp.status_code, 200) + data = parse_json(resp) + self.assertRegexpMatches(data['id'], + '^i4x:\/\/MITx\/999\/chapter\/([0-9]|[a-f]){32}$') + def test_capa_module(self): """Test that a problem treats markdown specially.""" CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index 74eff6e9cc..0191a97f6c 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -1,21 +1,27 @@ -from django.test.testcases import TestCase import datetime import time +import json +import calendar +import copy +from util import converters +from util.converters import jsdate_to_time + +from django.test.testcases import TestCase from django.contrib.auth.models import User -import xmodule from django.test.client import Client from django.core.urlresolvers import reverse -from xmodule.modulestore import Location -from cms.djangoapps.models.settings.course_details import CourseDetails,\ - CourseSettingsEncoder -import json -from util import converters -import calendar -from util.converters import jsdate_to_time from django.utils.timezone import UTC + +import xmodule +from xmodule.modulestore import Location +from cms.djangoapps.models.settings.course_details import (CourseDetails, + CourseSettingsEncoder) from cms.djangoapps.models.settings.course_grading import CourseGradingModel from cms.djangoapps.contentstore.utils import get_modulestore -import copy + +from utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory + # YYYY-MM-DDThh:mm:ss.s+/-HH:MM class ConvertersTestCase(TestCase): @@ -36,8 +42,15 @@ class ConvertersTestCase(TestCase): self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00:00"), converters.jsdate_to_time("2012-12-31T23:59:59"), datetime.timedelta(seconds=1)) -class CourseTestCase(TestCase): +class CourseTestCase(ModuleStoreTestCase): def setUp(self): + """ + These tests need a user in the DB so that the django Test Client + can log them in. + They inherit from the ModuleStoreTestCase class so that the mongodb collection + will be cleared out before each test case execution and deleted + afterwards. + """ uname = 'testuser' email = 'test+courses@edx.org' password = 'foo' @@ -52,36 +65,15 @@ class CourseTestCase(TestCase): self.user.is_staff = True self.user.save() - # Flush and initialize the module store - # It needs the templates because it creates new records - # by cloning from the template. - # Note that if your test module gets in some weird state - # (though it shouldn't), do this manually - # from the bash shell to drop it: - # $ mongo test_xmodule --eval "db.dropDatabase()" - xmodule.modulestore.django._MODULESTORES = {} - xmodule.modulestore.django.modulestore().collection.drop() - xmodule.templates.update_templates() - self.client = Client() self.client.login(username=uname, password=password) - self.course_data = { - 'template': 'i4x://edx/templates/course/Empty', - 'org': 'MITx', - 'number': '999', - 'display_name': 'Robot Super Course', - } - self.course_location = Location('i4x', 'MITx', '999', 'course', 'Robot_Super_Course') - self.create_course() - - def tearDown(self): - xmodule.modulestore.django._MODULESTORES = {} - xmodule.modulestore.django.modulestore().collection.drop() - - def create_course(self): - """Create new course""" - self.client.post(reverse('create_new_course'), self.course_data) + t='i4x://edx/templates/course/Empty' + o='MITx' + n='999' + dn='Robot Super Course' + self.course_location = Location('i4x', o, n, 'course', 'Robot_Super_Course') + CourseFactory.create(template=t, org=o, number=n, display_name=dn) class CourseDetailsTestCase(CourseTestCase): def test_virgin_fetch(self): @@ -145,7 +137,6 @@ class CourseDetailsViewTest(CourseTestCase): return datetime.isoformat("T") else: return None - def test_update_and_fetch(self): details = CourseDetails.fetch(self.course_location) @@ -271,5 +262,3 @@ class CourseGradingTest(CourseTestCase): test_grader.graders[1]['drop_count'] = test_grader.graders[1].get('drop_count') + 1 altered_grader = CourseGradingModel.update_grader_from_json(test_grader.course_location, test_grader.graders[1]) self.assertDictEqual(test_grader.graders[1], altered_grader, "drop_count[1] + 2") - - diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py index 8c8ee07264..1f09aee578 100644 --- a/cms/djangoapps/contentstore/tests/tests.py +++ b/cms/djangoapps/contentstore/tests/tests.py @@ -11,8 +11,6 @@ import json from fs.osfs import OSFS import copy -from student.models import Registration -from django.contrib.auth.models import User from cms.djangoapps.contentstore.utils import get_modulestore from xmodule.modulestore import Location @@ -29,22 +27,7 @@ from xmodule.course_module import CourseDescriptor from xmodule.seq_module import SequenceDescriptor from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory -from .utils import CmsTestCase - -def parse_json(response): - """Parse response, which is assumed to be json""" - return json.loads(response.content) - - -def user(email): - """look up a user by email""" - return User.objects.get(email=email) - - -def registration(email): - """look up registration object by email""" - return Registration.objects.get(user__email=email) - +from utils import ModuleStoreTestCase, parse_json, user, registration class ContentStoreTestCase(TestCase): def _login(self, email, pw): diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py index c800ee936c..bcf0471820 100644 --- a/cms/djangoapps/contentstore/tests/utils.py +++ b/cms/djangoapps/contentstore/tests/utils.py @@ -1,15 +1,20 @@ from django.test import TestCase -from xmodule.modulestore.django import modulestore, _MODULESTORES +import json + +from student.models import Registration +from django.contrib.auth.models import User + +import xmodule.modulestore.django from xmodule.templates import update_templates # Subclass TestCase and use to initialize the contentstore -class CmsTestCase(TestCase): +class ModuleStoreTestCase(TestCase): """ Subclass for any test case that uses the mongodb module store. This clears it out before running the TestCase and reinitilizes it with the templates afterwards. """ def _pre_setup(self): - super(CmsTestCase, self)._pre_setup() + super(ModuleStoreTestCase, self)._pre_setup() # Flush and initialize the module store # It needs the templates because it creates new records # by cloning from the template. @@ -17,16 +22,27 @@ class CmsTestCase(TestCase): # (though it shouldn't), do this manually # from the bash shell to drop it: # $ mongo test_xmodule --eval "db.dropDatabase()" - _MODULESTORES = {} - modulestore().collection.drop() + xmodule.modulestore.django._MODULESTORES = {} + xmodule.modulestore.django.modulestore().collection.drop() update_templates() def _post_teardown(self): # Make sure you flush out the test modulestore after the end - # of the last test because otherwise on the next run - # cms/djangoapps/contentstore/__init__.py - # update_templates() will try to update the templates - # via upsert and it sometimes seems to be messing things up. - _MODULESTORES = {} - modulestore().collection.drop() - super(CmsTestCase, self)._post_teardown() \ No newline at end of file + # of the last test so the collection will be deleted. + # Otherwise there will be lingering collections leftover + # from executing the tests. + xmodule.modulestore.django._MODULESTORES = {} + xmodule.modulestore.django.modulestore().collection.drop() + super(ModuleStoreTestCase, self)._post_teardown() + +def parse_json(response): + """Parse response, which is assumed to be json""" + return json.loads(response.content) + +def user(email): + """look up a user by email""" + return User.objects.get(email=email) + +def registration(email): + """look up registration object by email""" + return Registration.objects.get(user__email=email) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 7ebb2648ec..fb63cb34ed 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -1240,6 +1240,11 @@ def edge(request): @login_required @expect_json def create_new_course(request): + # This logic is repeated in xmodule/modulestore/tests/factories.py + # so if you change anything here, you need to also change it there. + # TODO: write a test that creates two courses, one with the factory and + # the other with this method, then compare them to make sure they are + # equivalent. template = Location(request.POST['template']) org = request.POST.get('org') number = request.POST.get('number') @@ -1289,8 +1294,11 @@ def initialize_course_tabs(course): # at least a list populated with the minimal times # @TODO: I don't like the fact that the presentation tier is away of these data related constraints, let's find a better # place for this. Also rather than using a simple list of dictionaries a nice class model would be helpful here - course.tabs = [{"type": "courseware"}, - {"type": "course_info", "name": "Course Info"}, + + # This logic is repeated in xmodule/modulestore/tests/factories.py + # so if you change anything here, you need to also change it there. + course.tabs = [{"type": "courseware"}, + {"type": "course_info", "name": "Course Info"}, {"type": "discussion", "name": "Discussion"}, {"type": "wiki", "name": "Wiki"}, {"type": "progress", "name": "Progress"}] diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index 987dbca09b..b4264b30c9 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -1,5 +1,4 @@ from factory import Factory -# from datetime import datetime from time import gmtime from uuid import uuid4 from xmodule.modulestore import Location @@ -22,7 +21,8 @@ class XModuleCourseFactory(Factory): @classmethod def _create(cls, target_class, *args, **kwargs): - + # This logic was taken from the create_new_course method in + # cms/djangoapps/contentstore/views.py template = Location('i4x', 'edx', 'templates', 'course', 'Empty') org = kwargs.get('org') number = kwargs.get('number') @@ -41,6 +41,7 @@ class XModuleCourseFactory(Factory): new_course.metadata['data_dir'] = uuid4().hex new_course.metadata['start'] = stringify_time(gmtime()) + new_course.tabs = [{"type": "courseware"}, {"type": "course_info", "name": "Course Info"}, {"type": "discussion", "name": "Discussion"}, @@ -114,4 +115,4 @@ class ItemFactory(XModuleItemFactory): parent_location = 'i4x://MITx/999/course/Robot_Super_Course' template = 'i4x://edx/templates/chapter/Empty' - display_name = 'Section One' \ No newline at end of file + display_name = 'Section One' From abf4ad36f48f664d0a3f64afe4b5d59127bd2b28 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Mon, 4 Feb 2013 17:01:38 -0500 Subject: [PATCH 128/444] Move modulestore unique naming in tests from the settings.py file into the ModuleStoreTestCase class. --- .../contentstore/tests/test_core_caching.py | 6 +---- .../tests/test_course_settings.py | 2 +- .../contentstore/tests/test_utils.py | 2 +- cms/djangoapps/contentstore/tests/tests.py | 3 +-- cms/djangoapps/contentstore/tests/utils.py | 27 ++++++++++++++----- cms/envs/test.py | 6 +---- 6 files changed, 26 insertions(+), 20 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_core_caching.py b/cms/djangoapps/contentstore/tests/test_core_caching.py index 0cb4a4930c..ed41e5cc64 100644 --- a/cms/djangoapps/contentstore/tests/test_core_caching.py +++ b/cms/djangoapps/contentstore/tests/test_core_caching.py @@ -1,7 +1,7 @@ -from django.test.testcases import TestCase from cache_toolbox.core import get_cached_content, set_cached_content, del_cached_content from xmodule.modulestore import Location from xmodule.contentstore.content import StaticContent +from django.test import TestCase class Content: def __init__(self, location, content): @@ -32,7 +32,3 @@ class CachingTestCase(TestCase): 'should not be stored in cache with unicodeLocation') self.assertEqual(None, get_cached_content(self.nonUnicodeLocation), 'should not be stored in cache with nonUnicodeLocation') - - - - diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index 0191a97f6c..f47733b32c 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -6,7 +6,6 @@ import copy from util import converters from util.converters import jsdate_to_time -from django.test.testcases import TestCase from django.contrib.auth.models import User from django.test.client import Client from django.core.urlresolvers import reverse @@ -19,6 +18,7 @@ from cms.djangoapps.models.settings.course_details import (CourseDetails, from cms.djangoapps.models.settings.course_grading import CourseGradingModel from cms.djangoapps.contentstore.utils import get_modulestore +from django.test import TestCase from utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py index 13f6189cc5..6811d64c12 100644 --- a/cms/djangoapps/contentstore/tests/test_utils.py +++ b/cms/djangoapps/contentstore/tests/test_utils.py @@ -1,6 +1,6 @@ -from django.test.testcases import TestCase from cms.djangoapps.contentstore import utils import mock +from django.test import TestCase class LMSLinksTestCase(TestCase): def about_page_test(self): diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py index 1f09aee578..df2e4dcc79 100644 --- a/cms/djangoapps/contentstore/tests/tests.py +++ b/cms/djangoapps/contentstore/tests/tests.py @@ -1,6 +1,5 @@ import json import shutil -from django.test import TestCase from django.test.client import Client from override_settings import override_settings from django.conf import settings @@ -29,7 +28,7 @@ from xmodule.seq_module import SequenceDescriptor from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from utils import ModuleStoreTestCase, parse_json, user, registration -class ContentStoreTestCase(TestCase): +class ContentStoreTestCase(ModuleStoreTestCase): def _login(self, email, pw): """Login. View should always return 200. The success/fail is in the returned json""" diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py index bcf0471820..7ed2b33e63 100644 --- a/cms/djangoapps/contentstore/tests/utils.py +++ b/cms/djangoapps/contentstore/tests/utils.py @@ -1,5 +1,9 @@ -from django.test import TestCase import json +import copy +from time import time +from django.test import TestCase +from override_settings import override_settings +from django.conf import settings from student.models import Registration from django.contrib.auth.models import User @@ -7,14 +11,23 @@ from django.contrib.auth.models import User import xmodule.modulestore.django from xmodule.templates import update_templates -# Subclass TestCase and use to initialize the contentstore class ModuleStoreTestCase(TestCase): """ Subclass for any test case that uses the mongodb module store. This clears it out before running the TestCase and reinitilizes it with the templates afterwards. """ def _pre_setup(self): - super(ModuleStoreTestCase, self)._pre_setup() + super(ModuleStoreTestCase, self)._pre_setup() + + # Use the current seconds since epoch to differentiate + # the mongo collections on jenkins. + sec_since_epoch = '%s' % int(time()*100) + self.orig_MODULESTORE = copy.deepcopy(settings.MODULESTORE) + self.test_MODULESTORE = self.orig_MODULESTORE + self.test_MODULESTORE['default']['OPTIONS']['collection'] = 'modulestore_%s' % sec_since_epoch + self.test_MODULESTORE['direct']['OPTIONS']['collection'] = 'modulestore_%s' % sec_since_epoch + settings.MODULESTORE = self.test_MODULESTORE + # Flush and initialize the module store # It needs the templates because it creates new records # by cloning from the template. @@ -27,12 +40,14 @@ class ModuleStoreTestCase(TestCase): update_templates() def _post_teardown(self): - # Make sure you flush out the test modulestore after the end - # of the last test so the collection will be deleted. - # Otherwise there will be lingering collections leftover + # Make sure you flush out the modulestore. + # Drop the collection at the end of the test, + # otherwise there will be lingering collections leftover # from executing the tests. xmodule.modulestore.django._MODULESTORES = {} xmodule.modulestore.django.modulestore().collection.drop() + settings.MODULESTORE = self.orig_MODULESTORE + super(ModuleStoreTestCase, self)._post_teardown() def parse_json(response): diff --git a/cms/envs/test.py b/cms/envs/test.py index c22965d748..74c3e349a4 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -10,7 +10,6 @@ sessions. Assumes structure: from .common import * import os from path import path -from time import time # Nose Test Runner INSTALLED_APPS += ('django_nose',) @@ -39,14 +38,11 @@ STATICFILES_DIRS += [ if os.path.isdir(COMMON_TEST_DATA_ROOT / course_dir) ] -# Use the current seconds since epoch to differentiate -# the mongo collections on jenkins. -sec_since_epoch = '%s' % int(time()*100) modulestore_options = { 'default_class': 'xmodule.raw_module.RawDescriptor', 'host': 'localhost', 'db': 'test_xmodule', - 'collection': 'modulestore_%s' % sec_since_epoch, + 'collection': 'modulestore', 'fs_root': GITHUB_REPO_ROOT, 'render_template': 'mitxmako.shortcuts.render_to_string', } From 72023cb5d2f45a85f406225a64ce2b57d95e6b19 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Mon, 4 Feb 2013 17:15:54 -0500 Subject: [PATCH 129/444] Improve docstring wording for ModuleStoreTestCase class. --- cms/djangoapps/contentstore/tests/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py index 7ed2b33e63..7fa5b76685 100644 --- a/cms/djangoapps/contentstore/tests/utils.py +++ b/cms/djangoapps/contentstore/tests/utils.py @@ -13,8 +13,9 @@ from xmodule.templates import update_templates class ModuleStoreTestCase(TestCase): """ Subclass for any test case that uses the mongodb - module store. This clears it out before running the TestCase - and reinitilizes it with the templates afterwards. """ + module store. This populates a uniquely named modulestore + collection with templates before running the TestCase + and drops it they are finished. """ def _pre_setup(self): super(ModuleStoreTestCase, self)._pre_setup() @@ -36,7 +37,6 @@ class ModuleStoreTestCase(TestCase): # from the bash shell to drop it: # $ mongo test_xmodule --eval "db.dropDatabase()" xmodule.modulestore.django._MODULESTORES = {} - xmodule.modulestore.django.modulestore().collection.drop() update_templates() def _post_teardown(self): From 0b650122fb97139206e798f39a2daf7340fb769b Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 5 Feb 2013 07:39:53 -0500 Subject: [PATCH 130/444] Config changes to make pep8 autofixes easier --- .pep8 | 2 ++ rakefile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 .pep8 diff --git a/.pep8 b/.pep8 new file mode 100644 index 0000000000..25d0edbcb4 --- /dev/null +++ b/.pep8 @@ -0,0 +1,2 @@ +[pep8] +ignore=E501 \ No newline at end of file diff --git a/rakefile b/rakefile index bf80b4a87a..05652edbba 100644 --- a/rakefile +++ b/rakefile @@ -134,7 +134,7 @@ end desc "Run pep8 on all #{system} code" task "pep8_#{system}" => report_dir do - sh("pep8 --ignore=E501 #{system}/djangoapps #{system}/lib | tee #{report_dir}/pep8.report") + sh("pep8 #{system} | tee #{report_dir}/pep8.report") end task :pep8 => "pep8_#{system}" From 7fc4081495d7e825447c5889a52a3ef3293f754a Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 5 Feb 2013 11:17:40 -0500 Subject: [PATCH 131/444] Make pylint work on bare .py files from rake --- rakefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rakefile b/rakefile index 05652edbba..5647b5b13a 100644 --- a/rakefile +++ b/rakefile @@ -147,6 +147,11 @@ end pythonpath_prefix = "PYTHONPATH=#{File.dirname(app)}" end app = File.basename(app) + if app =~ /.py$/ + app = app.gsub('.py', '') + elsif app =~ /.pyc$/ + next + end sh("#{pythonpath_prefix} pylint --rcfile=.pylintrc -f parseable #{app} | tee #{report_dir}/#{app}.pylint.report") end end From cfae1cdf62fb74307b88bd091cd4ba927af6b1fc Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 6 Feb 2013 11:13:50 -0500 Subject: [PATCH 132/444] Pep8 autofixes --- cms/__init__.py | 1 - cms/djangoapps/auth/authz.py | 21 +- .../contentstore/course_info_model.py | 49 +++-- .../contentstore/features/common.py | 35 +++- .../contentstore/features/courses.py | 20 +- .../contentstore/features/factories.py | 5 +- .../contentstore/features/section.py | 26 ++- .../contentstore/features/signup.py | 6 +- .../features/studio-overview-togglesection.py | 31 ++- .../contentstore/features/subsection.py | 16 +- .../contentstore/management/commands/clone.py | 1 + .../management/commands/delete_course.py | 4 +- .../management/commands/prompt.py | 3 +- .../contentstore/module_info_model.py | 4 +- .../contentstore/tests/factories.py | 5 + .../contentstore/tests/test_contentstore.py | 65 +++--- .../contentstore/tests/test_core_caching.py | 2 + .../tests/test_course_settings.py | 87 ++++---- .../contentstore/tests/test_course_updates.py | 29 +-- .../contentstore/tests/test_utils.py | 5 +- cms/djangoapps/contentstore/tests/tests.py | 1 + cms/djangoapps/contentstore/tests/utils.py | 8 +- cms/djangoapps/contentstore/utils.py | 11 +- cms/djangoapps/contentstore/views.py | 131 +++++++----- .../models/settings/course_details.py | 50 ++--- .../models/settings/course_grading.py | 134 ++++++------ cms/envs/acceptance.py | 6 +- cms/envs/common.py | 6 +- cms/envs/dev.py | 4 +- cms/envs/dev_ike.py | 6 +- cms/envs/test.py | 8 +- cms/manage.py | 2 +- cms/urls.py | 4 +- common/djangoapps/cache_toolbox/core.py | 4 + common/djangoapps/contentserver/middleware.py | 2 +- common/djangoapps/course_groups/cohorts.py | 7 +- common/djangoapps/course_groups/models.py | 3 +- .../djangoapps/course_groups/tests/tests.py | 7 +- common/djangoapps/course_groups/views.py | 3 + common/djangoapps/external_auth/admin.py | 3 +- common/djangoapps/external_auth/models.py | 14 +- .../tests/test_openid_provider.py | 68 +++--- common/djangoapps/external_auth/views.py | 16 +- common/djangoapps/mitxmako/makoloader.py | 26 +-- common/djangoapps/mitxmako/template.py | 12 +- .../mitxmako/templatetag_helpers.py | 19 +- common/djangoapps/static_replace/__init__.py | 1 + .../test/test_static_replace.py | 1 + common/djangoapps/status/__init__.py | 1 - common/djangoapps/status/status.py | 1 + .../management/commands/6002exportusers.py | 2 +- .../management/commands/add_to_group.py | 1 + .../commands/create_random_users.py | 2 + .../management/commands/pearson_dump.py | 33 ++- .../management/commands/pearson_export_cdd.py | 6 +- .../commands/pearson_import_conf_zip.py | 1 - .../commands/pearson_make_tc_registration.py | 63 +++--- .../commands/pearson_make_tc_user.py | 54 ++--- .../management/commands/pearson_transfer.py | 20 +- .../management/commands/tests/test_pearson.py | 193 +++++++++--------- .../migrations/0020_add_test_center_user.py | 2 +- .../student/migrations/0021_remove_askbot.py | 2 +- ...ed__add_unique_courseenrollmentallowed_.py | 2 +- .../0023_add_test_center_registration.py | 2 +- .../migrations/0024_add_allow_certificate.py | 2 +- common/djangoapps/student/models.py | 32 +-- common/djangoapps/student/tests.py | 3 +- common/djangoapps/student/views.py | 57 ++++-- .../track/migrations/0001_initial.py | 2 +- ...t__chg_field_trackinglog_event_type__ch.py | 2 +- common/djangoapps/track/models.py | 17 +- common/djangoapps/track/views.py | 18 +- common/djangoapps/util/cache.py | 1 - common/djangoapps/util/converters.py | 9 +- common/djangoapps/util/json_request.py | 2 +- common/djangoapps/util/views.py | 1 + common/djangoapps/xmodule_modifiers.py | 26 +-- common/lib/capa/capa/calc.py | 4 +- common/lib/capa/capa/capa_problem.py | 6 +- common/lib/capa/capa/chem/__init__.py | 1 - common/lib/capa/capa/chem/chemcalc.py | 24 +-- common/lib/capa/capa/correctmap.py | 4 +- common/lib/capa/capa/customrender.py | 4 +- common/lib/capa/capa/inputtypes.py | 23 ++- common/lib/capa/capa/responsetypes.py | 8 +- common/lib/capa/capa/tests/__init__.py | 5 +- .../lib/capa/capa/tests/test_customrender.py | 10 +- common/lib/capa/capa/tests/test_inputtypes.py | 17 +- .../lib/capa/capa/tests/test_responsetypes.py | 17 +- common/lib/capa/capa/util.py | 7 +- common/lib/capa/capa/xqueue_interface.py | 13 +- common/lib/supertrace.py | 5 +- common/lib/xmodule/setup.py | 2 +- common/lib/xmodule/xmodule/abtest_module.py | 6 +- common/lib/xmodule/xmodule/capa_module.py | 16 +- .../xmodule/combined_open_ended_module.py | 8 +- .../xmodule/combined_open_ended_rubric.py | 18 +- .../lib/xmodule/xmodule/conditional_module.py | 19 +- .../xmodule/xmodule/contentstore/content.py | 43 ++-- .../xmodule/xmodule/contentstore/django.py | 1 + .../lib/xmodule/xmodule/contentstore/mongo.py | 43 ++-- common/lib/xmodule/xmodule/course_module.py | 69 +++---- .../lib/xmodule/xmodule/discussion_module.py | 2 + common/lib/xmodule/xmodule/errortracker.py | 3 + common/lib/xmodule/xmodule/graders.py | 35 ++-- .../xmodule/xmodule/grading_service_module.py | 6 +- common/lib/xmodule/xmodule/html_checker.py | 1 + common/lib/xmodule/xmodule/html_module.py | 4 +- common/lib/xmodule/xmodule/mako_module.py | 3 +- .../xmodule/xmodule/modulestore/__init__.py | 2 +- .../lib/xmodule/xmodule/modulestore/draft.py | 2 +- .../lib/xmodule/xmodule/modulestore/mongo.py | 4 +- .../lib/xmodule/xmodule/modulestore/search.py | 4 +- .../xmodule/modulestore/store_utilities.py | 32 +-- .../xmodule/modulestore/tests/__init__.py | 2 - .../xmodule/modulestore/tests/factories.py | 18 +- .../modulestore/tests/test_location.py | 8 +- .../modulestore/tests/test_modulestore.py | 2 +- .../xmodule/modulestore/tests/test_mongo.py | 1 - .../xmodule/modulestore/tests/test_xml.py | 3 +- common/lib/xmodule/xmodule/modulestore/xml.py | 14 +- .../xmodule/modulestore/xml_exporter.py | 7 +- .../xmodule/modulestore/xml_importer.py | 80 ++++---- .../xmodule/open_ended_image_submission.py | 7 +- .../lib/xmodule/xmodule/open_ended_module.py | 5 +- common/lib/xmodule/xmodule/openendedchild.py | 10 +- .../xmodule/xmodule/peer_grading_module.py | 32 +-- .../xmodule/xmodule/peer_grading_service.py | 26 ++- .../lib/xmodule/xmodule/randomize_module.py | 4 +- common/lib/xmodule/xmodule/raw_module.py | 3 +- .../xmodule/xmodule/self_assessment_module.py | 1 + common/lib/xmodule/xmodule/seq_module.py | 7 +- common/lib/xmodule/xmodule/stringify.py | 1 + common/lib/xmodule/xmodule/template_module.py | 2 +- common/lib/xmodule/xmodule/tests/__init__.py | 5 +- .../xmodule/xmodule/tests/test_capa_module.py | 8 +- .../xmodule/tests/test_combined_open_ended.py | 45 ++-- .../xmodule/xmodule/tests/test_conditional.py | 15 +- .../lib/xmodule/xmodule/tests/test_content.py | 4 +- .../lib/xmodule/xmodule/tests/test_export.py | 7 +- .../lib/xmodule/xmodule/tests/test_graders.py | 2 +- .../lib/xmodule/xmodule/tests/test_import.py | 5 +- .../xmodule/xmodule/tests/test_progress.py | 1 + .../xmodule/tests/test_randomize_module.py | 1 - .../xmodule/tests/test_self_assessment.py | 4 +- .../xmodule/xmodule/tests/test_stringify.py | 2 + common/lib/xmodule/xmodule/timeparse.py | 2 + common/lib/xmodule/xmodule/util/decorators.py | 10 +- common/lib/xmodule/xmodule/vertical_module.py | 2 +- common/lib/xmodule/xmodule/video_module.py | 2 +- common/lib/xmodule/xmodule/x_module.py | 8 +- common/lib/xmodule/xmodule/xml_module.py | 32 +-- .../docs/source/conf.py | 3 +- common/xml_cleanup.py | 12 +- ...el_field_generatedcertificate_name__add.py | 2 +- ...icate_graded_download_url__del_field_ge.py | 2 +- ...icate_enabled__add_field_generatedcerti.py | 2 +- ...icate_certificate_id__add_field_generat.py | 2 +- ...icate_name__add_field_generatedcertific.py | 2 +- ...field_generatedcertificate_error_reason.py | 2 +- .../migrations/0014_adding_whitelist.py | 2 +- lms/djangoapps/certificates/models.py | 2 + lms/djangoapps/certificates/queue.py | 2 +- lms/djangoapps/course_wiki/course_nav.py | 62 +++--- lms/djangoapps/course_wiki/editors.py | 9 +- .../plugins/markdownedx/__init__.py | 2 +- .../plugins/markdownedx/mdx_mathjax.py | 1 - .../plugins/markdownedx/wiki_plugin.py | 6 +- lms/djangoapps/course_wiki/tests/__init__.py | 1 - lms/djangoapps/course_wiki/tests/tests.py | 100 +++++---- lms/djangoapps/course_wiki/views.py | 34 +-- lms/djangoapps/courseware/access.py | 6 +- lms/djangoapps/courseware/admin.py | 1 - lms/djangoapps/courseware/courses.py | 7 +- lms/djangoapps/courseware/features/courses.py | 20 +- .../courseware/features/courseware.py | 3 +- .../courseware/features/courseware_common.py | 9 +- .../courseware/features/openended.py | 21 +- .../courseware/features/smart-accordion.py | 15 +- lms/djangoapps/courseware/grades.py | 54 ++--- .../management/commands/clean_xml.py | 1 + .../management/commands/metadata_to_json.py | 3 + ..._add_unique_offlinecomputedgrade_user_c.py | 2 +- lms/djangoapps/courseware/models.py | 4 +- lms/djangoapps/courseware/module_render.py | 40 ++-- lms/djangoapps/courseware/tabs.py | 25 ++- lms/djangoapps/courseware/tests/__init__.py | 1 - lms/djangoapps/courseware/tests/factories.py | 5 + .../courseware/tests/test_access.py | 11 +- lms/djangoapps/courseware/tests/tests.py | 52 ++--- lms/djangoapps/courseware/views.py | 26 ++- lms/djangoapps/dashboard/views.py | 10 +- .../django_comment_client/base/urls.py | 2 +- .../django_comment_client/base/views.py | 52 +++-- .../django_comment_client/forum/views.py | 35 ++-- .../django_comment_client/helpers.py | 5 + .../commands/assign_roles_for_course.py | 1 + .../commands/create_roles_for_existing.py | 1 + .../commands/seed_permissions_roles.py | 2 +- .../django_comment_client/middleware.py | 3 +- .../migrations/0001_initial.py | 2 +- .../django_comment_client/models.py | 4 +- .../django_comment_client/mustache_helpers.py | 7 +- .../django_comment_client/permissions.py | 10 +- .../django_comment_client/settings.py | 2 +- .../tests/test_helpers.py | 3 +- .../tests/test_middleware.py | 33 +-- .../tests/test_mustache_helpers.py | 32 +-- .../django_comment_client/tests/test_utils.py | 18 +- lms/djangoapps/django_comment_client/utils.py | 43 +++- .../management/commands/compute_grades.py | 11 +- .../management/commands/dump_grades.py | 24 +-- .../instructor/offline_gradecalc.py | 20 +- lms/djangoapps/instructor/tests.py | 33 ++- lms/djangoapps/instructor/views.py | 132 ++++++------ .../licenses/migrations/0001_initial.py | 2 +- .../management/commands/create_groups.py | 14 +- .../management/commands/create_user.py | 68 +++--- .../commands/manage_course_groups.py | 39 ++-- lms/djangoapps/lms_migration/migrate.py | 52 ++--- .../controller_query_service.py | 36 ++-- .../open_ended_notifications.py | 50 +++-- .../open_ended_grading/open_ended_util.py | 5 +- .../open_ended_grading/staff_grading.py | 1 - .../staff_grading_service.py | 9 +- lms/djangoapps/open_ended_grading/tests.py | 13 +- lms/djangoapps/open_ended_grading/views.py | 54 ++--- lms/djangoapps/portal/features/common.py | 20 +- lms/djangoapps/portal/features/factories.py | 3 + lms/djangoapps/portal/features/homepage.py | 1 + lms/djangoapps/portal/features/login.py | 10 +- .../portal/features/registration.py | 3 + lms/djangoapps/portal/features/signup.py | 5 +- .../management/commands/init_psychometrics.py | 26 +-- lms/djangoapps/psychometrics/models.py | 6 +- lms/djangoapps/psychometrics/psychoanalyze.py | 12 +- lms/djangoapps/simplewiki/mdx_mathjax.py | 1 - lms/djangoapps/simplewiki/views.py | 1 + lms/djangoapps/static_template_view/views.py | 1 - lms/djangoapps/staticbook/views.py | 6 +- lms/djangoapps/terrain/__init__.py | 2 +- lms/djangoapps/terrain/browser.py | 7 +- lms/djangoapps/terrain/factories.py | 23 ++- lms/djangoapps/terrain/steps.py | 40 +++- lms/envs/acceptance.py | 6 +- lms/envs/common.py | 52 ++--- lms/envs/content.py | 6 +- lms/envs/dev.py | 2 +- lms/envs/dev_edx4edx.py | 10 +- lms/envs/dev_ike.py | 16 +- lms/envs/dev_int.py | 6 +- lms/envs/devgroups/courses.py | 25 +-- lms/envs/devgroups/portal.py | 1 - lms/envs/devplus.py | 10 +- lms/envs/edx4edx_aws.py | 14 +- lms/envs/static.py | 4 +- lms/envs/test.py | 4 +- lms/envs/test_ike.py | 6 +- lms/lib/comment_client/comment.py | 3 + lms/lib/comment_client/comment_client.py | 8 + lms/lib/comment_client/commentable.py | 1 + lms/lib/comment_client/legacy.py | 52 ++++- lms/lib/comment_client/models.py | 11 +- lms/lib/comment_client/thread.py | 3 +- lms/lib/comment_client/user.py | 6 + lms/lib/comment_client/utils.py | 9 +- lms/lib/dogfood/check.py | 2 - lms/lib/loncapa/loncapa_check.py | 2 - lms/lib/symmath/symmath_check.py | 8 +- lms/static/admin/js/compress.py | 1 + lms/urls.py | 16 +- 271 files changed, 2395 insertions(+), 1826 deletions(-) diff --git a/cms/__init__.py b/cms/__init__.py index 8b13789179..e69de29bb2 100644 --- a/cms/__init__.py +++ b/cms/__init__.py @@ -1 +0,0 @@ - diff --git a/cms/djangoapps/auth/authz.py b/cms/djangoapps/auth/authz.py index 22bbc4bc1c..281e3f46b2 100644 --- a/cms/djangoapps/auth/authz.py +++ b/cms/djangoapps/auth/authz.py @@ -18,6 +18,8 @@ STAFF_ROLE_NAME = 'staff' # we're just making a Django group for each location/role combo # to do this we're just creating a Group name which is a formatted string # of those two variables + + def get_course_groupname_for_role(location, role): loc = Location(location) # hack: check for existence of a group name in the legacy LMS format _ @@ -25,11 +27,12 @@ def get_course_groupname_for_role(location, role): # more information groupname = '{0}_{1}'.format(role, loc.course) - if len(Group.objects.filter(name = groupname)) == 0: - groupname = '{0}_{1}'.format(role,loc.course_id) + if len(Group.objects.filter(name=groupname)) == 0: + groupname = '{0}_{1}'.format(role, loc.course_id) return groupname + def get_users_in_course_group_by_role(location, role): groupname = get_course_groupname_for_role(location, role) (group, created) = Group.objects.get_or_create(name=groupname) @@ -39,6 +42,8 @@ def get_users_in_course_group_by_role(location, role): ''' Create all permission groups for a new course and subscribe the caller into those roles ''' + + def create_all_course_groups(creator, location): create_new_course_group(creator, location, INSTRUCTOR_ROLE_NAME) create_new_course_group(creator, location, STAFF_ROLE_NAME) @@ -46,7 +51,7 @@ def create_all_course_groups(creator, location): def create_new_course_group(creator, location, role): groupname = get_course_groupname_for_role(location, role) - (group, created) =Group.objects.get_or_create(name=groupname) + (group, created) = Group.objects.get_or_create(name=groupname) if created: group.save() @@ -59,6 +64,8 @@ def create_new_course_group(creator, location, role): This is to be called only by either a command line code path or through a app which has already asserted permissions ''' + + def _delete_course_group(location): # remove all memberships instructors = Group.objects.get(name=get_course_groupname_for_role(location, INSTRUCTOR_ROLE_NAME)) @@ -75,6 +82,8 @@ def _delete_course_group(location): This is to be called only by either a command line code path or through an app which has already asserted permissions to do this action ''' + + def _copy_course_group(source, dest): instructors = Group.objects.get(name=get_course_groupname_for_role(source, INSTRUCTOR_ROLE_NAME)) new_instructors_group = Group.objects.get(name=get_course_groupname_for_role(dest, INSTRUCTOR_ROLE_NAME)) @@ -86,7 +95,7 @@ def _copy_course_group(source, dest): new_staff_group = Group.objects.get(name=get_course_groupname_for_role(dest, STAFF_ROLE_NAME)) for user in staff.user_set.all(): user.groups.add(new_staff_group) - user.save() + user.save() def add_user_to_course_group(caller, user, location, role): @@ -133,8 +142,6 @@ def remove_user_from_course_group(caller, user, location, role): def is_user_in_course_group_role(user, location, role): if user.is_active and user.is_authenticated: # all "is_staff" flagged accounts belong to all groups - return user.is_staff or user.groups.filter(name=get_course_groupname_for_role(location,role)).count() > 0 + return user.is_staff or user.groups.filter(name=get_course_groupname_for_role(location, role)).count() > 0 return False - - diff --git a/cms/djangoapps/contentstore/course_info_model.py b/cms/djangoapps/contentstore/course_info_model.py index 6995df06a8..153d13dd13 100644 --- a/cms/djangoapps/contentstore/course_info_model.py +++ b/cms/djangoapps/contentstore/course_info_model.py @@ -8,6 +8,8 @@ import logging ## TODO store as array of { date, content } and override course_info_module.definition_from_xml ## This should be in a class which inherits from XmlDescriptor + + def get_course_updates(location): """ Retrieve the relevant course_info updates and unpack into the model which the client expects: @@ -21,13 +23,13 @@ def get_course_updates(location): # current db rep: {"_id" : locationjson, "definition" : { "data" : "
      [
    1. date

      content
    2. ]
    "} "metadata" : ignored} location_base = course_updates.location.url() - + # purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break. try: course_html_parsed = html.fromstring(course_updates.definition['data']) except: course_html_parsed = html.fromstring("
      ") - + # Confirm that root is
        , iterate over
      1. , pull out

        subs and then rest of val course_upd_collection = [] if course_html_parsed.tag == 'ol': @@ -40,25 +42,26 @@ def get_course_updates(location): content = update[0].tail else: content = "\n".join([html.tostring(ele) for ele in update[1:]]) - + # make the id on the client be 1..len w/ 1 being the oldest and len being the newest - course_upd_collection.append({"id" : location_base + "/" + str(len(course_html_parsed) - idx), - "date" : update.findtext("h2"), - "content" : content}) - + course_upd_collection.append({"id": location_base + "/" + str(len(course_html_parsed) - idx), + "date": update.findtext("h2"), + "content": content}) + return course_upd_collection + def update_course_updates(location, update, passed_id=None): """ Either add or update the given course update. It will add it if the passed_id is absent or None. It will update it if it has an passed_id which has a valid value. Until updates have distinct values, the passed_id is the location url + an index - into the html structure. + into the html structure. """ try: course_updates = modulestore('direct').get_item(location) except ItemNotFoundError: return HttpResponseBadRequest - + # purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break. try: course_html_parsed = html.fromstring(course_updates.definition['data']) @@ -67,7 +70,7 @@ def update_course_updates(location, update, passed_id=None): # No try/catch b/c failure generates an error back to client new_html_parsed = html.fromstring('
      2. ' + update['date'] + '

        ' + update['content'] + '
      3. ') - + # Confirm that root is
          , iterate over
        1. , pull out

          subs and then rest of val if course_html_parsed.tag == 'ol': # ??? Should this use the id in the json or in the url or does it matter? @@ -80,14 +83,15 @@ def update_course_updates(location, update, passed_id=None): idx = len(course_html_parsed) passed_id = course_updates.location.url() + "/" + str(idx) - + # update db record course_updates.definition['data'] = html.tostring(course_html_parsed) modulestore('direct').update_item(location, course_updates.definition['data']) - - return {"id" : passed_id, - "date" : update['date'], - "content" :update['content']} + + return {"id": passed_id, + "date": update['date'], + "content": update['content']} + def delete_course_update(location, update, passed_id): """ @@ -96,19 +100,19 @@ def delete_course_update(location, update, passed_id): """ if not passed_id: return HttpResponseBadRequest - + try: course_updates = modulestore('direct').get_item(location) except ItemNotFoundError: return HttpResponseBadRequest - + # TODO use delete_blank_text parser throughout and cache as a static var in a class # purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break. try: course_html_parsed = html.fromstring(course_updates.definition['data']) except: course_html_parsed = html.fromstring("
            ") - + if course_html_parsed.tag == 'ol': # ??? Should this use the id in the json or in the url or does it matter? idx = get_idx(passed_id) @@ -120,10 +124,11 @@ def delete_course_update(location, update, passed_id): # update db record course_updates.definition['data'] = html.tostring(course_html_parsed) store = modulestore('direct') - store.update_item(location, course_updates.definition['data']) - + store.update_item(location, course_updates.definition['data']) + return get_course_updates(location) - + + def get_idx(passed_id): """ From the url w/ idx appended, get the idx. @@ -131,4 +136,4 @@ def get_idx(passed_id): # TODO compile this regex into a class static and reuse for each call idx_matcher = re.search(r'.*/(\d+)$', passed_id) if idx_matcher: - return int(idx_matcher.group(1)) \ No newline at end of file + return int(idx_matcher.group(1)) diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index d910d73085..f868b598a8 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -12,6 +12,8 @@ from logging import getLogger logger = getLogger(__name__) ########### STEP HELPERS ############## + + @step('I (?:visit|access|open) the Studio homepage$') def i_visit_the_studio_homepage(step): # To make this go to port 8001, put @@ -20,32 +22,37 @@ def i_visit_the_studio_homepage(step): world.browser.visit(django_url('/')) assert world.browser.is_element_present_by_css('body.no-header', 10) + @step('I am logged into Studio$') def i_am_logged_into_studio(step): log_into_studio() + @step('I confirm the alert$') def i_confirm_with_ok(step): world.browser.get_alert().accept() + @step(u'I press the "([^"]*)" delete icon$') def i_press_the_category_delete_icon(step, category): if category == 'section': css = 'a.delete-button.delete-section-button span.delete-icon' elif category == 'subsection': - css='a.delete-button.delete-subsection-button span.delete-icon' + css = 'a.delete-button.delete-subsection-button span.delete-icon' else: assert False, 'Invalid category: %s' % category css_click(css) ####### HELPER FUNCTIONS ############## + + def create_studio_user( uname='robot', email='robot+studio@edx.org', password='test', - is_staff=False): + is_staff=False): studio_user = UserFactory.build( - username=uname, + username=uname, email=email, password=password, is_staff=is_staff) @@ -58,6 +65,7 @@ def create_studio_user( user_profile = UserProfileFactory(user=studio_user) + def flush_xmodule_store(): # Flush and initialize the module store # It needs the templates because it creates new records @@ -70,26 +78,32 @@ def flush_xmodule_store(): xmodule.modulestore.django.modulestore().collection.drop() xmodule.templates.update_templates() -def assert_css_with_text(css,text): + +def assert_css_with_text(css, text): assert_true(world.browser.is_element_present_by_css(css, 5)) assert_equal(world.browser.find_by_css(css).text, text) + def css_click(css): world.browser.find_by_css(css).first.click() + def css_fill(css, value): world.browser.find_by_css(css).first.fill(value) + def clear_courses(): flush_xmodule_store() + def fill_in_course_info( name='Robot Super Course', org='MITx', num='101'): - css_fill('.new-course-name',name) - css_fill('.new-course-org',org) - css_fill('.new-course-number',num) + css_fill('.new-course-name', name) + css_fill('.new-course-org', org) + css_fill('.new-course-number', num) + def log_into_studio( uname='robot', @@ -108,24 +122,27 @@ def log_into_studio( assert_true(world.browser.is_element_present_by_css('.new-course-button', 5)) + def create_a_course(): css_click('a.new-course-button') fill_in_course_info() css_click('input.new-course-save') assert_true(world.browser.is_element_present_by_css('a#courseware-tab', 5)) + def add_section(name='My Section'): link_css = 'a.new-courseware-section-button' css_click(link_css) name_css = '.new-section-name' save_css = '.new-section-name-save' - css_fill(name_css,name) + css_fill(name_css, name) css_click(save_css) + def add_subsection(name='Subsection One'): css = 'a.new-subsection-item' css_click(css) name_css = 'input.new-subsection-name-input' save_css = 'input.new-subsection-name-save' css_fill(name_css, name) - css_click(save_css) \ No newline at end of file + css_click(save_css) diff --git a/cms/djangoapps/contentstore/features/courses.py b/cms/djangoapps/contentstore/features/courses.py index 2c1cf6281a..d2d038a928 100644 --- a/cms/djangoapps/contentstore/features/courses.py +++ b/cms/djangoapps/contentstore/features/courses.py @@ -2,49 +2,61 @@ from lettuce import world, step from common import * ############### ACTIONS #################### + + @step('There are no courses$') def no_courses(step): clear_courses() + @step('I click the New Course button$') def i_click_new_course(step): css_click('.new-course-button') + @step('I fill in the new course information$') def i_fill_in_a_new_course_information(step): fill_in_course_info() + @step('I create a new course$') def i_create_a_course(step): create_a_course() + @step('I click the course link in My Courses$') def i_click_the_course_link_in_my_courses(step): course_css = 'span.class-name' css_click(course_css) ############ ASSERTIONS ################### + + @step('the Courseware page has loaded in Studio$') def courseware_page_has_loaded_in_studio(step): courseware_css = 'a#courseware-tab' assert world.browser.is_element_present_by_css(courseware_css) + @step('I see the course listed in My Courses$') def i_see_the_course_in_my_courses(step): course_css = 'span.class-name' - assert_css_with_text(course_css,'Robot Super Course') + assert_css_with_text(course_css, 'Robot Super Course') + @step('the course is loaded$') def course_is_loaded(step): class_css = 'a.class-name' - assert_css_with_text(class_css,'Robot Super Course') + assert_css_with_text(class_css, 'Robot Super Course') + @step('I am on the "([^"]*)" tab$') def i_am_on_tab(step, tab_name): header_css = 'div.inner-wrapper h1' - assert_css_with_text(header_css,tab_name) + assert_css_with_text(header_css, tab_name) + @step('I see a link for adding a new section$') def i_see_new_section_link(step): link_css = 'a.new-courseware-section-button' - assert_css_with_text(link_css,'New Section') + assert_css_with_text(link_css, 'New Section') diff --git a/cms/djangoapps/contentstore/features/factories.py b/cms/djangoapps/contentstore/features/factories.py index 389f2bac49..087ceaaa2d 100644 --- a/cms/djangoapps/contentstore/features/factories.py +++ b/cms/djangoapps/contentstore/features/factories.py @@ -3,6 +3,7 @@ from student.models import User, UserProfile, Registration from datetime import datetime import uuid + class UserProfileFactory(factory.Factory): FACTORY_FOR = UserProfile @@ -10,12 +11,14 @@ class UserProfileFactory(factory.Factory): name = 'Robot Studio' courseware = 'course.xml' + class RegistrationFactory(factory.Factory): FACTORY_FOR = Registration user = None activation_key = uuid.uuid4().hex + class UserFactory(factory.Factory): FACTORY_FOR = User @@ -28,4 +31,4 @@ class UserFactory(factory.Factory): is_active = True is_superuser = False last_login = datetime.now() - date_joined = datetime.now() \ No newline at end of file + date_joined = datetime.now() diff --git a/cms/djangoapps/contentstore/features/section.py b/cms/djangoapps/contentstore/features/section.py index 8ac30e2170..3bcaeab6c4 100644 --- a/cms/djangoapps/contentstore/features/section.py +++ b/cms/djangoapps/contentstore/features/section.py @@ -2,54 +2,65 @@ from lettuce import world, step from common import * ############### ACTIONS #################### + + @step('I have opened a new course in Studio$') def i_have_opened_a_new_course(step): clear_courses() log_into_studio() create_a_course() + @step('I click the new section link$') def i_click_new_section_link(step): link_css = 'a.new-courseware-section-button' css_click(link_css) + @step('I enter the section name and click save$') def i_save_section_name(step): name_css = '.new-section-name' save_css = '.new-section-name-save' - css_fill(name_css,'My Section') + css_fill(name_css, 'My Section') css_click(save_css) + @step('I have added a new section$') def i_have_added_new_section(step): add_section() - + + @step('I click the Edit link for the release date$') def i_click_the_edit_link_for_the_release_date(step): button_css = 'div.section-published-date a.edit-button' css_click(button_css) + @step('I save a new section release date$') def i_save_a_new_section_release_date(step): date_css = 'input.start-date.date.hasDatepicker' time_css = 'input.start-time.time.ui-timepicker-input' - css_fill(date_css,'12/25/2013') + css_fill(date_css, '12/25/2013') # click here to make the calendar go away css_click(time_css) - css_fill(time_css,'12:00am') + css_fill(time_css, '12:00am') css_click('a.save-button') ############ ASSERTIONS ################### + + @step('I see my section on the Courseware page$') def i_see_my_section_on_the_courseware_page(step): section_css = 'span.section-name-span' - assert_css_with_text(section_css,'My Section') + assert_css_with_text(section_css, 'My Section') + @step('the section does not exist$') def section_does_not_exist(step): css = 'span.section-name-span' assert world.browser.is_element_not_present_by_css(css) + @step('I see a release date for my section$') def i_see_a_release_date_for_my_section(step): import re @@ -63,18 +74,21 @@ def i_see_a_release_date_for_my_section(step): date_regex = '[01][0-9]\/[0-3][0-9]\/[12][0-9][0-9][0-9]' time_regex = '[0-2][0-9]:[0-5][0-9]' match_string = '%s %s at %s' % (msg, date_regex, time_regex) - assert re.match(match_string,status_text) + assert re.match(match_string, status_text) + @step('I see a link to create a new subsection$') def i_see_a_link_to_create_a_new_subsection(step): css = 'a.new-subsection-item' assert world.browser.is_element_present_by_css(css) + @step('the section release date picker is not visible$') def the_section_release_date_picker_not_visible(step): css = 'div.edit-subsection-publish-settings' assert False, world.browser.find_by_css(css).visible + @step('the section release date is updated$') def the_section_release_date_is_updated(step): css = 'span.published-status' diff --git a/cms/djangoapps/contentstore/features/signup.py b/cms/djangoapps/contentstore/features/signup.py index 7794511f94..e105b674f7 100644 --- a/cms/djangoapps/contentstore/features/signup.py +++ b/cms/djangoapps/contentstore/features/signup.py @@ -1,5 +1,6 @@ from lettuce import world, step + @step('I fill in the registration form$') def i_fill_in_the_registration_form(step): register_form = world.browser.find_by_css('form#register_form') @@ -9,15 +10,18 @@ def i_fill_in_the_registration_form(step): register_form.find_by_name('name').fill('Robot Studio') register_form.find_by_name('terms_of_service').check() + @step('I press the "([^"]*)" button on the registration form$') def i_press_the_button_on_the_registration_form(step, button): register_form = world.browser.find_by_css('form#register_form') register_form.find_by_value(button).click() + @step('I should see be on the studio home page$') def i_should_see_be_on_the_studio_home_page(step): assert world.browser.find_by_css('div.inner-wrapper') + @step(u'I should see the message "([^"]*)"$') def i_should_see_the_message(step, msg): - assert world.browser.is_text_present(msg, 5) \ No newline at end of file + assert world.browser.is_text_present(msg, 5) diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py index 010678c0e8..00aa39455d 100644 --- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py +++ b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py @@ -6,11 +6,13 @@ from nose.tools import assert_true, assert_false, assert_equal from logging import getLogger logger = getLogger(__name__) + @step(u'I have a course with no sections$') def have_a_course(step): clear_courses() course = CourseFactory.create() + @step(u'I have a course with 1 section$') def have_a_course_with_1_section(step): clear_courses() @@ -18,8 +20,9 @@ def have_a_course_with_1_section(step): section = ItemFactory.create(parent_location=course.location) subsection1 = ItemFactory.create( parent_location=section.location, - template = 'i4x://edx/templates/sequential/Empty', - display_name = 'Subsection One',) + template='i4x://edx/templates/sequential/Empty', + display_name='Subsection One',) + @step(u'I have a course with multiple sections$') def have_a_course_with_two_sections(step): @@ -28,19 +31,20 @@ def have_a_course_with_two_sections(step): section = ItemFactory.create(parent_location=course.location) subsection1 = ItemFactory.create( parent_location=section.location, - template = 'i4x://edx/templates/sequential/Empty', - display_name = 'Subsection One',) + template='i4x://edx/templates/sequential/Empty', + display_name='Subsection One',) section2 = ItemFactory.create( parent_location=course.location, display_name='Section Two',) subsection2 = ItemFactory.create( parent_location=section2.location, - template = 'i4x://edx/templates/sequential/Empty', - display_name = 'Subsection Alpha',) + template='i4x://edx/templates/sequential/Empty', + display_name='Subsection Alpha',) subsection3 = ItemFactory.create( parent_location=section2.location, - template = 'i4x://edx/templates/sequential/Empty', - display_name = 'Subsection Beta',) + template='i4x://edx/templates/sequential/Empty', + display_name='Subsection Beta',) + @step(u'I navigate to the course overview page$') def navigate_to_the_course_overview_page(step): @@ -48,15 +52,18 @@ def navigate_to_the_course_overview_page(step): course_locator = '.class-name' css_click(course_locator) + @step(u'I navigate to the courseware page of a course with multiple sections') def nav_to_the_courseware_page_of_a_course_with_multiple_sections(step): step.given('I have a course with multiple sections') step.given('I navigate to the course overview page') + @step(u'I add a section') def i_add_a_section(step): add_section(name='My New Section That I Just Added') + @step(u'I click the "([^"]*)" link$') def i_click_the_text_span(step, text): span_locator = '.toggle-button-sections span' @@ -65,16 +72,19 @@ def i_click_the_text_span(step, text): assert_equal(world.browser.find_by_css(span_locator).value, text) css_click(span_locator) + @step(u'I collapse the first section$') def i_collapse_a_section(step): collapse_locator = 'section.courseware-section a.collapse' css_click(collapse_locator) + @step(u'I expand the first section$') def i_expand_a_section(step): expand_locator = 'section.courseware-section a.expand' css_click(expand_locator) + @step(u'I see the "([^"]*)" link$') def i_see_the_span_with_text(step, text): span_locator = '.toggle-button-sections span' @@ -82,6 +92,7 @@ def i_see_the_span_with_text(step, text): assert_equal(world.browser.find_by_css(span_locator).value, text) assert_true(world.browser.find_by_css(span_locator).visible) + @step(u'I do not see the "([^"]*)" link$') def i_do_not_see_the_span_with_text(step, text): # Note that the span will exist on the page but not be visible @@ -89,6 +100,7 @@ def i_do_not_see_the_span_with_text(step, text): assert_true(world.browser.is_element_present_by_css(span_locator)) assert_false(world.browser.find_by_css(span_locator).visible) + @step(u'all sections are expanded$') def all_sections_are_expanded(step): subsection_locator = 'div.subsection-list' @@ -96,9 +108,10 @@ def all_sections_are_expanded(step): for s in subsections: assert_true(s.visible) + @step(u'all sections are collapsed$') def all_sections_are_expanded(step): subsection_locator = 'div.subsection-list' subsections = world.browser.find_by_css(subsection_locator) for s in subsections: - assert_false(s.visible) \ No newline at end of file + assert_false(s.visible) diff --git a/cms/djangoapps/contentstore/features/subsection.py b/cms/djangoapps/contentstore/features/subsection.py index ea614d3feb..e2041b8dbf 100644 --- a/cms/djangoapps/contentstore/features/subsection.py +++ b/cms/djangoapps/contentstore/features/subsection.py @@ -2,6 +2,8 @@ from lettuce import world, step from common import * ############### ACTIONS #################### + + @step('I have opened a new course section in Studio$') def i_have_opened_a_new_course_section(step): clear_courses() @@ -9,31 +11,37 @@ def i_have_opened_a_new_course_section(step): create_a_course() add_section() + @step('I click the New Subsection link') def i_click_the_new_subsection_link(step): css = 'a.new-subsection-item' css_click(css) + @step('I enter the subsection name and click save$') def i_save_subsection_name(step): name_css = 'input.new-subsection-name-input' save_css = 'input.new-subsection-name-save' - css_fill(name_css,'Subsection One') + css_fill(name_css, 'Subsection One') css_click(save_css) + @step('I have added a new subsection$') def i_have_added_a_new_subsection(step): add_subsection() ############ ASSERTIONS ################### + + @step('I see my subsection on the Courseware page$') def i_see_my_subsection_on_the_courseware_page(step): css = 'span.subsection-name' - assert world.browser.is_element_present_by_css(css) + assert world.browser.is_element_present_by_css(css) css = 'span.subsection-name-value' - assert_css_with_text(css,'Subsection One') + assert_css_with_text(css, 'Subsection One') + @step('the subsection does not exist$') def the_subsection_does_not_exist(step): css = 'span.subsection-name' - assert world.browser.is_element_not_present_by_css(css) \ No newline at end of file + assert world.browser.is_element_not_present_by_css(css) diff --git a/cms/djangoapps/contentstore/management/commands/clone.py b/cms/djangoapps/contentstore/management/commands/clone.py index 2357cd1dbd..abf04f3da3 100644 --- a/cms/djangoapps/contentstore/management/commands/clone.py +++ b/cms/djangoapps/contentstore/management/commands/clone.py @@ -14,6 +14,7 @@ from auth.authz import _copy_course_group # To run from command line: rake cms:clone SOURCE_LOC=MITx/111/Foo1 DEST_LOC=MITx/135/Foo3 # + class Command(BaseCommand): help = \ '''Clone a MongoDB backed course to another location''' diff --git a/cms/djangoapps/contentstore/management/commands/delete_course.py b/cms/djangoapps/contentstore/management/commands/delete_course.py index 0313f7faed..bb38e72d44 100644 --- a/cms/djangoapps/contentstore/management/commands/delete_course.py +++ b/cms/djangoapps/contentstore/management/commands/delete_course.py @@ -15,6 +15,7 @@ from auth.authz import _delete_course_group # To run from command line: rake cms:delete_course LOC=MITx/111/Foo1 # + class Command(BaseCommand): help = \ '''Delete a MongoDB backed course''' @@ -35,6 +36,3 @@ class Command(BaseCommand): print 'removing User permissions from course....' # in the django layer, we need to remove all the user permissions groups associated with this course _delete_course_group(loc) - - - diff --git a/cms/djangoapps/contentstore/management/commands/prompt.py b/cms/djangoapps/contentstore/management/commands/prompt.py index 9c8fd81d45..211c48406c 100644 --- a/cms/djangoapps/contentstore/management/commands/prompt.py +++ b/cms/djangoapps/contentstore/management/commands/prompt.py @@ -1,5 +1,6 @@ import sys + def query_yes_no(question, default="yes"): """Ask a yes/no question via raw_input() and return their answer. @@ -30,4 +31,4 @@ def query_yes_no(question, default="yes"): return valid[choice] else: sys.stdout.write("Please respond with 'yes' or 'no' "\ - "(or 'y' or 'n').\n") \ No newline at end of file + "(or 'y' or 'n').\n") diff --git a/cms/djangoapps/contentstore/module_info_model.py b/cms/djangoapps/contentstore/module_info_model.py index 3b783c8815..796184baa0 100644 --- a/cms/djangoapps/contentstore/module_info_model.py +++ b/cms/djangoapps/contentstore/module_info_model.py @@ -7,7 +7,8 @@ from lxml import etree import re from django.http import HttpResponseBadRequest, Http404 -def get_module_info(store, location, parent_location = None, rewrite_static_links = False): + +def get_module_info(store, location, parent_location=None, rewrite_static_links=False): try: if location.revision is None: module = store.get_item(location) @@ -36,6 +37,7 @@ def get_module_info(store, location, parent_location = None, rewrite_static_link 'metadata': module.metadata } + def set_module_info(store, location, post_data): module = None isNew = False diff --git a/cms/djangoapps/contentstore/tests/factories.py b/cms/djangoapps/contentstore/tests/factories.py index f9c505d68f..d15610f11c 100644 --- a/cms/djangoapps/contentstore/tests/factories.py +++ b/cms/djangoapps/contentstore/tests/factories.py @@ -5,6 +5,7 @@ from student.models import (User, UserProfile, Registration, CourseEnrollmentAllowed) from django.contrib.auth.models import Group + class UserProfileFactory(Factory): FACTORY_FOR = UserProfile @@ -12,12 +13,14 @@ class UserProfileFactory(Factory): name = 'Robot Studio' courseware = 'course.xml' + class RegistrationFactory(Factory): FACTORY_FOR = Registration user = None activation_key = uuid4().hex + class UserFactory(Factory): FACTORY_FOR = User @@ -32,11 +35,13 @@ class UserFactory(Factory): last_login = datetime.now() date_joined = datetime.now() + class GroupFactory(Factory): FACTORY_FOR = Group name = 'test_group' + class CourseEnrollmentAllowedFactory(Factory): FACTORY_FOR = CourseEnrollmentAllowed diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index ce7d9e757c..72ae3821cc 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -35,6 +35,7 @@ TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE) TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data') + @override_settings(MODULESTORE=TEST_DATA_MODULESTORE) class ContentStoreToyCourseTest(ModuleStoreTestCase): """ @@ -77,20 +78,20 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): def test_static_tab_reordering(self): import_from_xml(modulestore(), 'common/test/data/', ['full']) - + ms = modulestore('direct') - course = ms.get_item(Location(['i4x','edX','full','course','6.002_Spring_2012', None])) + course = ms.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) # reverse the ordering reverse_tabs = [] for tab in course.tabs: if tab['type'] == 'static_tab': reverse_tabs.insert(0, 'i4x://edX/full/static_tab/{0}'.format(tab['url_slug'])) - - resp = self.client.post(reverse('reorder_static_tabs'), json.dumps({'tabs':reverse_tabs}), "application/json") - course = ms.get_item(Location(['i4x','edX','full','course','6.002_Spring_2012', None])) - + resp = self.client.post(reverse('reorder_static_tabs'), json.dumps({'tabs': reverse_tabs}), "application/json") + + course = ms.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) + # compare to make sure that the tabs information is in the expected order after the server call course_tabs = [] for tab in course.tabs: @@ -101,17 +102,17 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): def test_about_overrides(self): ''' - This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html + This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html while there is a base definition in /about/effort.html ''' import_from_xml(modulestore(), 'common/test/data/', ['full']) ms = modulestore('direct') - effort = ms.get_item(Location(['i4x','edX','full','about','effort', None])) - self.assertEqual(effort.definition['data'],'6 hours') + effort = ms.get_item(Location(['i4x', 'edX', 'full', 'about', 'effort', None])) + self.assertEqual(effort.definition['data'], '6 hours') # this one should be in a non-override folder - effort = ms.get_item(Location(['i4x','edX','full','about','end_date', None])) - self.assertEqual(effort.definition['data'],'TBD') + effort = ms.get_item(Location(['i4x', 'edX', 'full', 'about', 'end_date', None])) + self.assertEqual(effort.definition['data'], 'TBD') def test_remove_hide_progress_tab(self): import_from_xml(modulestore(), 'common/test/data/', ['full']) @@ -147,14 +148,14 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): clone_course(ms, cs, source_location, dest_location) - # now loop through all the units in the course and verify that the clone can render them, which + # now loop through all the units in the course and verify that the clone can render them, which # means the objects are at least present - items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None])) + items = ms.get_items(Location(['i4x', 'edX', 'full', 'vertical', None])) self.assertGreater(len(items), 0) - clone_items = ms.get_items(Location(['i4x', 'MITx','999','vertical', None])) + clone_items = ms.get_items(Location(['i4x', 'MITx', '999', 'vertical', None])) self.assertGreater(len(clone_items), 0) for descriptor in items: - new_loc = descriptor.location._replace(org = 'MITx', course='999') + new_loc = descriptor.location._replace(org='MITx', course='999') print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url()) resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()})) self.assertEqual(resp.status_code, 200) @@ -169,7 +170,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): delete_course(ms, cs, location) - items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None])) + items = ms.get_items(Location(['i4x', 'edX', 'full', 'vertical', None])) self.assertEqual(len(items), 0) def verify_content_existence(self, modulestore, root_dir, location, dirname, category_name, filename_suffix=''): @@ -185,7 +186,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): def test_export_course(self): ms = modulestore('direct') - cs = contentstore() + cs = contentstore() import_from_xml(ms, 'common/test/data/', ['full']) location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012') @@ -205,7 +206,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): # check for custom_tags self.verify_content_existence(ms, root_dir, location, 'custom_tags', 'custom_tag_template') - + # remove old course delete_course(ms, cs, location) @@ -213,23 +214,23 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): # reimport import_from_xml(ms, root_dir, ['test_export']) - items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None])) + items = ms.get_items(Location(['i4x', 'edX', 'full', 'vertical', None])) self.assertGreater(len(items), 0) for descriptor in items: print "Checking {0}....".format(descriptor.location.url()) resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()})) self.assertEqual(resp.status_code, 200) - shutil.rmtree(root_dir) + shutil.rmtree(root_dir) def test_course_handouts_rewrites(self): ms = modulestore('direct') - cs = contentstore() + cs = contentstore() # import a test course - import_from_xml(ms, 'common/test/data/', ['full']) + import_from_xml(ms, 'common/test/data/', ['full']) - handout_location= Location(['i4x', 'edX', 'full', 'course_info', 'handouts']) + handout_location = Location(['i4x', 'edX', 'full', 'course_info', 'handouts']) # get module info resp = self.client.get(reverse('module_info', kwargs={'module_location': handout_location})) @@ -239,7 +240,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): # check that /static/ has been converted to the full path # note, we know the link it should be because that's what in the 'full' course in the test data - self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf') + self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf') class ContentStoreTest(ModuleStoreTestCase): @@ -302,7 +303,7 @@ class ContentStoreTest(ModuleStoreTestCase): data = parse_json(resp) self.assertEqual(resp.status_code, 200) - self.assertEqual(data['ErrMsg'], + self.assertEqual(data['ErrMsg'], 'There is already a course defined with the same organization and course number.') def test_create_course_with_bad_organization(self): @@ -319,7 +320,7 @@ class ContentStoreTest(ModuleStoreTestCase): """Test viewing the index page with no courses""" # Create a course so there is something to view resp = self.client.get(reverse('index')) - self.assertContains(resp, + self.assertContains(resp, '

            My Courses

            ', status_code=200, html=True) @@ -355,7 +356,7 @@ class ContentStoreTest(ModuleStoreTestCase): } resp = self.client.get(reverse('course_index', kwargs=data)) - self.assertContains(resp, + self.assertContains(resp, 'Robot Super Course', status_code=200, html=True) @@ -365,8 +366,8 @@ class ContentStoreTest(ModuleStoreTestCase): CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') section_data = { - 'parent_location' : 'i4x://MITx/999/course/Robot_Super_Course', - 'template' : 'i4x://edx/templates/chapter/Empty', + 'parent_location': 'i4x://MITx/999/course/Robot_Super_Course', + 'template': 'i4x://edx/templates/chapter/Empty', 'display_name': 'Section One', } @@ -374,7 +375,7 @@ class ContentStoreTest(ModuleStoreTestCase): self.assertEqual(resp.status_code, 200) data = parse_json(resp) - self.assertRegexpMatches(data['id'], + self.assertRegexpMatches(data['id'], '^i4x:\/\/MITx\/999\/chapter\/([0-9]|[a-f]){32}$') def test_capa_module(self): @@ -382,8 +383,8 @@ class ContentStoreTest(ModuleStoreTestCase): CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') problem_data = { - 'parent_location' : 'i4x://MITx/999/course/Robot_Super_Course', - 'template' : 'i4x://edx/templates/problem/Empty' + 'parent_location': 'i4x://MITx/999/course/Robot_Super_Course', + 'template': 'i4x://edx/templates/problem/Empty' } resp = self.client.post(reverse('clone_item'), problem_data) diff --git a/cms/djangoapps/contentstore/tests/test_core_caching.py b/cms/djangoapps/contentstore/tests/test_core_caching.py index ed41e5cc64..676627a045 100644 --- a/cms/djangoapps/contentstore/tests/test_core_caching.py +++ b/cms/djangoapps/contentstore/tests/test_core_caching.py @@ -3,6 +3,7 @@ from xmodule.modulestore import Location from xmodule.contentstore.content import StaticContent from django.test import TestCase + class Content: def __init__(self, location, content): self.location = location @@ -11,6 +12,7 @@ class Content: def get_id(self): return StaticContent.get_id_from_location(self.location) + class CachingTestCase(TestCase): # Tests for https://edx.lighthouseapp.com/projects/102637/tickets/112-updating-asset-does-not-refresh-the-cached-copy unicodeLocation = Location(u'c4x', u'mitX', u'800', u'thumbnail', u'monsters.jpg') diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index f47733b32c..84e79b9670 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -27,21 +27,21 @@ from xmodule.modulestore.tests.factories import CourseFactory class ConvertersTestCase(TestCase): @staticmethod def struct_to_datetime(struct_time): - return datetime.datetime(struct_time.tm_year, struct_time.tm_mon, struct_time.tm_mday, struct_time.tm_hour, - struct_time.tm_min, struct_time.tm_sec, tzinfo = UTC()) - + return datetime.datetime(struct_time.tm_year, struct_time.tm_mon, struct_time.tm_mday, struct_time.tm_hour, + struct_time.tm_min, struct_time.tm_sec, tzinfo=UTC()) + def compare_dates(self, date1, date2, expected_delta): dt1 = ConvertersTestCase.struct_to_datetime(date1) dt2 = ConvertersTestCase.struct_to_datetime(date2) self.assertEqual(dt1 - dt2, expected_delta, str(date1) + "-" + str(date2) + "!=" + str(expected_delta)) - + def test_iso_to_struct(self): self.compare_dates(converters.jsdate_to_time("2013-01-01"), converters.jsdate_to_time("2012-12-31"), datetime.timedelta(days=1)) self.compare_dates(converters.jsdate_to_time("2013-01-01T00"), converters.jsdate_to_time("2012-12-31T23"), datetime.timedelta(hours=1)) self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00"), converters.jsdate_to_time("2012-12-31T23:59"), datetime.timedelta(minutes=1)) self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00:00"), converters.jsdate_to_time("2012-12-31T23:59:59"), datetime.timedelta(seconds=1)) - - + + class CourseTestCase(ModuleStoreTestCase): def setUp(self): """ @@ -68,13 +68,14 @@ class CourseTestCase(ModuleStoreTestCase): self.client = Client() self.client.login(username=uname, password=password) - t='i4x://edx/templates/course/Empty' - o='MITx' - n='999' - dn='Robot Super Course' + t = 'i4x://edx/templates/course/Empty' + o = 'MITx' + n = '999' + dn = 'Robot Super Course' self.course_location = Location('i4x', o, n, 'course', 'Robot_Super_Course') CourseFactory.create(template=t, org=o, number=n, display_name=dn) + class CourseDetailsTestCase(CourseTestCase): def test_virgin_fetch(self): details = CourseDetails.fetch(self.course_location) @@ -86,7 +87,7 @@ class CourseDetailsTestCase(CourseTestCase): self.assertEqual(details.overview, "", "overview somehow initialized" + details.overview) self.assertIsNone(details.intro_video, "intro_video somehow initialized" + str(details.intro_video)) self.assertIsNone(details.effort, "effort somehow initialized" + str(details.effort)) - + def test_encoder(self): details = CourseDetails.fetch(self.course_location) jsondetails = json.dumps(details, cls=CourseSettingsEncoder) @@ -100,7 +101,7 @@ class CourseDetailsTestCase(CourseTestCase): self.assertEqual(jsondetails['overview'], "", "overview somehow initialized") self.assertIsNone(jsondetails['intro_video'], "intro_video somehow initialized") self.assertIsNone(jsondetails['effort'], "effort somehow initialized") - + def test_update_and_fetch(self): ## NOTE: I couldn't figure out how to validly test time setting w/ all the conversions jsondetails = CourseDetails.fetch(self.course_location) @@ -118,6 +119,7 @@ class CourseDetailsTestCase(CourseTestCase): self.assertEqual(CourseDetails.update_from_json(jsondetails.__dict__).effort, jsondetails.effort, "After set effort") + class CourseDetailsViewTest(CourseTestCase): def alter_field(self, url, details, field, val): setattr(details, field, val) @@ -128,36 +130,36 @@ class CourseDetailsViewTest(CourseTestCase): payload['end_date'] = CourseDetailsViewTest.convert_datetime_to_iso(details.end_date) payload['enrollment_start'] = CourseDetailsViewTest.convert_datetime_to_iso(details.enrollment_start) payload['enrollment_end'] = CourseDetailsViewTest.convert_datetime_to_iso(details.enrollment_end) - resp = self.client.post(url, json.dumps(payload), "application/json") + resp = self.client.post(url, json.dumps(payload), "application/json") self.compare_details_with_encoding(json.loads(resp.content), details.__dict__, field + str(val)) - + @staticmethod def convert_datetime_to_iso(datetime): if datetime is not None: return datetime.isoformat("T") else: return None - + def test_update_and_fetch(self): details = CourseDetails.fetch(self.course_location) - - resp = self.client.get(reverse('course_settings', kwargs={'org' : self.course_location.org, 'course' : self.course_location.course, - 'name' : self.course_location.name })) + + resp = self.client.get(reverse('course_settings', kwargs={'org': self.course_location.org, 'course': self.course_location.course, + 'name': self.course_location.name})) self.assertContains(resp, '
          1. Course Details
          2. ', status_code=200, html=True) - # resp s/b json from here on - url = reverse('course_settings', kwargs={'org' : self.course_location.org, 'course' : self.course_location.course, - 'name' : self.course_location.name, 'section' : 'details' }) + # resp s/b json from here on + url = reverse('course_settings', kwargs={'org': self.course_location.org, 'course': self.course_location.course, + 'name': self.course_location.name, 'section': 'details'}) resp = self.client.get(url) self.compare_details_with_encoding(json.loads(resp.content), details.__dict__, "virgin get") utc = UTC() - self.alter_field(url, details, 'start_date', datetime.datetime(2012,11,12,1,30, tzinfo=utc)) - self.alter_field(url, details, 'start_date', datetime.datetime(2012,11,1,13,30, tzinfo=utc)) - self.alter_field(url, details, 'end_date', datetime.datetime(2013,2,12,1,30, tzinfo=utc)) - self.alter_field(url, details, 'enrollment_start', datetime.datetime(2012,10,12,1,30, tzinfo=utc)) + self.alter_field(url, details, 'start_date', datetime.datetime(2012, 11, 12, 1, 30, tzinfo=utc)) + self.alter_field(url, details, 'start_date', datetime.datetime(2012, 11, 1, 13, 30, tzinfo=utc)) + self.alter_field(url, details, 'end_date', datetime.datetime(2013, 2, 12, 1, 30, tzinfo=utc)) + self.alter_field(url, details, 'enrollment_start', datetime.datetime(2012, 10, 12, 1, 30, tzinfo=utc)) - self.alter_field(url, details, 'enrollment_end', datetime.datetime(2012,11,15,1,30, tzinfo=utc)) + self.alter_field(url, details, 'enrollment_end', datetime.datetime(2012, 11, 15, 1, 30, tzinfo=utc)) self.alter_field(url, details, 'overview', "Overview") self.alter_field(url, details, 'intro_video', "intro_video") self.alter_field(url, details, 'effort', "effort") @@ -170,7 +172,7 @@ class CourseDetailsViewTest(CourseTestCase): self.assertEqual(details['overview'], encoded['overview'], context + " overviews not ==") self.assertEqual(details['intro_video'], encoded.get('intro_video', None), context + " intro_video not ==") self.assertEqual(details['effort'], encoded['effort'], context + " efforts not ==") - + def compare_date_fields(self, details, encoded, context, field): if details[field] is not None: if field in encoded and encoded[field] is not None: @@ -182,14 +184,15 @@ class CourseDetailsViewTest(CourseTestCase): else: details_encoded = jsdate_to_time(details[field]) dt2 = ConvertersTestCase.struct_to_datetime(details_encoded) - + expected_delta = datetime.timedelta(0) self.assertEqual(dt1 - dt2, expected_delta, str(dt1) + "!=" + str(dt2) + " at " + context) else: self.fail(field + " missing from encoded but in details at " + context) elif field in encoded and encoded[field] is not None: self.fail(field + " included in encoding but missing from details at " + context) - + + class CourseGradingTest(CourseTestCase): def test_initial_grader(self): descriptor = get_modulestore(self.course_location).get_item(self.course_location) @@ -209,56 +212,56 @@ class CourseGradingTest(CourseTestCase): self.assertEqual(self.course_location, test_grader.course_location, "Course locations") self.assertIsNotNone(test_grader.graders, "No graders") self.assertIsNotNone(test_grader.grade_cutoffs, "No cutoffs") - + for i, grader in enumerate(test_grader.graders): subgrader = CourseGradingModel.fetch_grader(self.course_location, i) self.assertDictEqual(grader, subgrader, str(i) + "th graders not equal") - + subgrader = CourseGradingModel.fetch_grader(self.course_location.list(), 0) self.assertDictEqual(test_grader.graders[0], subgrader, "failed with location as list") - + def test_fetch_cutoffs(self): test_grader = CourseGradingModel.fetch_cutoffs(self.course_location) # ??? should this check that it's at least a dict? (expected is { "pass" : 0.5 } I think) self.assertIsNotNone(test_grader, "No cutoffs via fetch") - + test_grader = CourseGradingModel.fetch_cutoffs(self.course_location.url()) self.assertIsNotNone(test_grader, "No cutoffs via fetch with url") - + def test_fetch_grace(self): test_grader = CourseGradingModel.fetch_grace_period(self.course_location) # almost a worthless test self.assertIn('grace_period', test_grader, "No grace via fetch") - + test_grader = CourseGradingModel.fetch_grace_period(self.course_location.url()) self.assertIn('grace_period', test_grader, "No cutoffs via fetch with url") - + def test_update_from_json(self): test_grader = CourseGradingModel.fetch(self.course_location) altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "Noop update") - + test_grader.graders[0]['weight'] = test_grader.graders[0].get('weight') * 2 altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "Weight[0] * 2") - + test_grader.grade_cutoffs['D'] = 0.3 altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "cutoff add D") - + test_grader.grace_period = {'hours' : '4'} altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "4 hour grace period") - + def test_update_grader_from_json(self): test_grader = CourseGradingModel.fetch(self.course_location) altered_grader = CourseGradingModel.update_grader_from_json(test_grader.course_location, test_grader.graders[1]) self.assertDictEqual(test_grader.graders[1], altered_grader, "Noop update") - + test_grader.graders[1]['min_count'] = test_grader.graders[1].get('min_count') + 2 altered_grader = CourseGradingModel.update_grader_from_json(test_grader.course_location, test_grader.graders[1]) self.assertDictEqual(test_grader.graders[1], altered_grader, "min_count[1] + 2") - + test_grader.graders[1]['drop_count'] = test_grader.graders[1].get('drop_count') + 1 altered_grader = CourseGradingModel.update_grader_from_json(test_grader.course_location, test_grader.graders[1]) self.assertDictEqual(test_grader.graders[1], altered_grader, "drop_count[1] + 2") diff --git a/cms/djangoapps/contentstore/tests/test_course_updates.py b/cms/djangoapps/contentstore/tests/test_course_updates.py index 96e4468b31..c57f1322f5 100644 --- a/cms/djangoapps/contentstore/tests/test_course_updates.py +++ b/cms/djangoapps/contentstore/tests/test_course_updates.py @@ -2,29 +2,30 @@ from cms.djangoapps.contentstore.tests.test_course_settings import CourseTestCas from django.core.urlresolvers import reverse import json + class CourseUpdateTest(CourseTestCase): def test_course_update(self): # first get the update to force the creation - url = reverse('course_info', kwargs={'org' : self.course_location.org, 'course' : self.course_location.course, - 'name' : self.course_location.name }) + url = reverse('course_info', kwargs={'org': self.course_location.org, 'course': self.course_location.course, + 'name': self.course_location.name}) self.client.get(url) content = '' - payload = { 'content' : content, - 'date' : 'January 8, 2013'} - url = reverse('course_info', kwargs={'org' : self.course_location.org, 'course' : self.course_location.course, - 'provided_id' : ''}) - + payload = {'content': content, + 'date': 'January 8, 2013'} + url = reverse('course_info', kwargs={'org': self.course_location.org, 'course': self.course_location.course, + 'provided_id': ''}) + resp = self.client.post(url, json.dumps(payload), "application/json") - - payload= json.loads(resp.content) - + + payload = json.loads(resp.content) + self.assertHTMLEqual(content, payload['content'], "single iframe") - - url = reverse('course_info', kwargs={'org' : self.course_location.org, 'course' : self.course_location.course, - 'provided_id' : payload['id']}) + + url = reverse('course_info', kwargs={'org': self.course_location.org, 'course': self.course_location.course, + 'provided_id': payload['id']}) content += '
            div

            p

            ' payload['content'] = content resp = self.client.post(url, json.dumps(payload), "application/json") - + self.assertHTMLEqual(content, json.loads(resp.content)['content'], "iframe w/ div") diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py index 6811d64c12..09e3b045f9 100644 --- a/cms/djangoapps/contentstore/tests/test_utils.py +++ b/cms/djangoapps/contentstore/tests/test_utils.py @@ -2,15 +2,16 @@ from cms.djangoapps.contentstore import utils import mock from django.test import TestCase + class LMSLinksTestCase(TestCase): def about_page_test(self): - location = 'i4x','mitX','101','course', 'test' + location = 'i4x', 'mitX', '101', 'course', 'test' utils.get_course_id = mock.Mock(return_value="mitX/101/test") link = utils.get_lms_link_for_about_page(location) self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/about") def ls_link_test(self): - location = 'i4x','mitX','101','vertical', 'contacting_us' + location = 'i4x', 'mitX', '101', 'vertical', 'contacting_us' utils.get_course_id = mock.Mock(return_value="mitX/101/test") link = utils.get_lms_link_for_item(location, False) self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us") diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py index df2e4dcc79..9af5b09276 100644 --- a/cms/djangoapps/contentstore/tests/tests.py +++ b/cms/djangoapps/contentstore/tests/tests.py @@ -28,6 +28,7 @@ from xmodule.seq_module import SequenceDescriptor from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from utils import ModuleStoreTestCase, parse_json, user, registration + class ContentStoreTestCase(ModuleStoreTestCase): def _login(self, email, pw): """Login. View should always return 200. The success/fail is in the diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py index 7fa5b76685..4e3510463f 100644 --- a/cms/djangoapps/contentstore/tests/utils.py +++ b/cms/djangoapps/contentstore/tests/utils.py @@ -11,8 +11,9 @@ from django.contrib.auth.models import User import xmodule.modulestore.django from xmodule.templates import update_templates + class ModuleStoreTestCase(TestCase): - """ Subclass for any test case that uses the mongodb + """ Subclass for any test case that uses the mongodb module store. This populates a uniquely named modulestore collection with templates before running the TestCase and drops it they are finished. """ @@ -22,7 +23,7 @@ class ModuleStoreTestCase(TestCase): # Use the current seconds since epoch to differentiate # the mongo collections on jenkins. - sec_since_epoch = '%s' % int(time()*100) + sec_since_epoch = '%s' % int(time() * 100) self.orig_MODULESTORE = copy.deepcopy(settings.MODULESTORE) self.test_MODULESTORE = self.orig_MODULESTORE self.test_MODULESTORE['default']['OPTIONS']['collection'] = 'modulestore_%s' % sec_since_epoch @@ -50,14 +51,17 @@ class ModuleStoreTestCase(TestCase): super(ModuleStoreTestCase, self)._post_teardown() + def parse_json(response): """Parse response, which is assumed to be json""" return json.loads(response.content) + def user(email): """look up a user by email""" return User.objects.get(email=email) + def registration(email): """look up registration object by email""" return Registration.objects.get(user__email=email) diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index da2993e463..b14dd8b353 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -5,18 +5,20 @@ from xmodule.modulestore.exceptions import ItemNotFoundError DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential', 'about', 'static_tab', 'course_info'] + def get_modulestore(location): """ Returns the correct modulestore to use for modifying the specified location """ if not isinstance(location, Location): location = Location(location) - + if location.category in DIRECT_ONLY_CATEGORIES: return modulestore('direct') else: return modulestore() + def get_course_location_for_item(location): ''' cdodge: for a given Xmodule, return the course that it belongs to @@ -46,6 +48,7 @@ def get_course_location_for_item(location): return location + def get_course_for_item(location): ''' cdodge: for a given Xmodule, return the course that it belongs to @@ -85,6 +88,7 @@ def get_lms_link_for_item(location, preview=False): return lms_link + def get_lms_link_for_about_page(location): """ Returns the url to the course about page from the location tuple. @@ -99,6 +103,7 @@ def get_lms_link_for_about_page(location): return lms_link + def get_course_id(location): """ Returns the course_id from a given the location tuple. @@ -106,6 +111,7 @@ def get_course_id(location): # TODO: These will need to be changed to point to the particular instance of this problem in the particular course return modulestore().get_containing_courses(Location(location))[0].id + class UnitState(object): draft = 'draft' private = 'private' @@ -135,6 +141,7 @@ def compute_unit_state(unit): def get_date_display(date): return date.strftime("%d %B, %Y at %I:%M %p") + def update_item(location, value): """ If value is None, delete the db entry. Otherwise, update it using the correct modulestore. @@ -142,4 +149,4 @@ def update_item(location, value): if value is None: get_modulestore(location).delete_item(location) else: - get_modulestore(location).update_item(location, value) \ No newline at end of file + get_modulestore(location).update_item(location, value) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index fb63cb34ed..137e71b24a 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -81,6 +81,7 @@ def signup(request): csrf_token = csrf(request)['csrf_token'] return render_to_response('signup.html', {'csrf': csrf_token}) + @ssl_login_shortcut @ensure_csrf_cookie def login_page(request): @@ -114,7 +115,7 @@ def index(request): courses = filter(course_filter, courses) return render_to_response('index.html', { - 'new_course_template' : Location('i4x', 'edx', 'templates', 'course', 'Empty'), + 'new_course_template': Location('i4x', 'edx', 'templates', 'course', 'Empty'), 'courses': [(course.metadata.get('display_name'), reverse('course_index', args=[ course.location.org, @@ -159,10 +160,10 @@ def course_index(request, org, course, name): if not has_access(request.user, location): raise PermissionDenied() - upload_asset_callback_url = reverse('upload_asset', kwargs = { - 'org' : org, - 'course' : course, - 'coursename' : name + upload_asset_callback_url = reverse('upload_asset', kwargs={ + 'org': org, + 'course': course, + 'coursename': name }) course = modulestore().get_item(location) @@ -213,7 +214,7 @@ def edit_subsection(request, location): # remove all metadata from the generic dictionary that is presented in a more normalized UI - policy_metadata = dict((key,value) for key, value in item.metadata.iteritems() + policy_metadata = dict((key, value) for key, value in item.metadata.iteritems() if key not in ['display_name', 'start', 'due', 'format'] and key not in item.system_metadata_fields) can_view_live = False @@ -233,9 +234,9 @@ def edit_subsection(request, location): 'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders), 'parent_location': course.location, 'parent_item': parent, - 'policy_metadata' : policy_metadata, - 'subsection_units' : subsection_units, - 'can_view_live' : can_view_live + 'policy_metadata': policy_metadata, + 'subsection_units': subsection_units, + 'can_view_live': can_view_live }) @@ -294,7 +295,7 @@ def edit_unit(request, location): # so let's generate the link url here # need to figure out where this item is in the list of children as the preview will need this - index =1 + index = 1 for child in containing_subsection.get_children(): if child.location == item.location: break @@ -348,6 +349,7 @@ def preview_component(request, location): 'editor': wrap_xmodule(component.get_html, component, 'xmodule_edit.html')(), }) + @expect_json @login_required @ensure_csrf_cookie @@ -362,7 +364,7 @@ def assignment_type_update(request, org, course, category, name): if request.method == 'GET': return HttpResponse(json.dumps(CourseGradingModel.get_section_grader_type(location)), mimetype="application/json") - elif request.method == 'POST': # post or put, doesn't matter. + elif request.method == 'POST': # post or put, doesn't matter. return HttpResponse(json.dumps(CourseGradingModel.update_section_grader_type(location, request.POST)), mimetype="application/json") @@ -527,7 +529,7 @@ def load_preview_module(request, preview_id, descriptor, instance_state, shared_ module.get_html = replace_static_urls( module.get_html, module.metadata.get('data_dir', module.location.course), - course_namespace = Location([module.location.tag, module.location.org, module.location.course, None, None]) + course_namespace=Location([module.location.tag, module.location.org, module.location.course, None, None]) ) save_preview_state(request, preview_id, descriptor.location.url(), module.get_instance_state(), module.get_shared_state()) @@ -588,7 +590,7 @@ def delete_item(request): # semantics of delete_item whereby the store is draft aware. Right now calling # delete_item on a vertical tries to delete the draft version leaving the # requested delete to never occur - if item.location.revision is None and item.location.category=='vertical' and delete_all_versions: + if item.location.revision is None and item.location.category == 'vertical' and delete_all_versions: modulestore('direct').delete_item(item.location) return HttpResponse() @@ -664,6 +666,7 @@ def create_draft(request): return HttpResponse() + @login_required @expect_json def publish_draft(request): @@ -693,6 +696,7 @@ def unpublish_unit(request): return HttpResponse() + @login_required @expect_json def clone_item(request): @@ -725,6 +729,8 @@ def clone_item(request): #@login_required #@ensure_csrf_cookie + + def upload_asset(request, org, course, coursename): ''' cdodge: this method allows for POST uploading of files into the course asset library, which will @@ -775,11 +781,11 @@ def upload_asset(request, org, course, coursename): # readback the saved content - we need the database timestamp readback = contentstore().find(content.location) - response_payload = {'displayname' : content.name, - 'uploadDate' : get_date_display(readback.last_modified_at), - 'url' : StaticContent.get_url_path_from_location(content.location), - 'thumb_url' : StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None, - 'msg' : 'Upload completed' + response_payload = {'displayname': content.name, + 'uploadDate': get_date_display(readback.last_modified_at), + 'url': StaticContent.get_url_path_from_location(content.location), + 'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None, + 'msg': 'Upload completed' } response = HttpResponse(json.dumps(response_payload)) @@ -789,6 +795,8 @@ def upload_asset(request, org, course, coursename): ''' This view will return all CMS users who are editors for the specified course ''' + + @login_required @ensure_csrf_cookie def manage_users(request, location): @@ -803,16 +811,16 @@ def manage_users(request, location): 'active_tab': 'users', 'context_course': course_module, 'staff': get_users_in_course_group_by_role(location, STAFF_ROLE_NAME), - 'add_user_postback_url' : reverse('add_user', args=[location]).rstrip('/'), - 'remove_user_postback_url' : reverse('remove_user', args=[location]).rstrip('/'), - 'allow_actions' : has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME), - 'request_user_id' : request.user.id + 'add_user_postback_url': reverse('add_user', args=[location]).rstrip('/'), + 'remove_user_postback_url': reverse('remove_user', args=[location]).rstrip('/'), + 'allow_actions': has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME), + 'request_user_id': request.user.id }) -def create_json_response(errmsg = None): +def create_json_response(errmsg=None): if errmsg is not None: - resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg' : errmsg})) + resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg': errmsg})) else: resp = HttpResponse(json.dumps({'Status': 'OK'})) @@ -822,13 +830,15 @@ def create_json_response(errmsg = None): This POST-back view will add a user - specified by email - to the list of editors for the specified course ''' + + @expect_json @login_required @ensure_csrf_cookie def add_user(request, location): email = request.POST["email"] - if email=='': + if email == '': return create_json_response('Please specify an email address.') # check that logged in user has admin permissions to this course @@ -854,6 +864,8 @@ def add_user(request, location): This POST-back view will remove a user - specified by email - from the list of editors for the specified course ''' + + @expect_json @login_required @ensure_csrf_cookie @@ -881,6 +893,7 @@ def remove_user(request, location): def landing(request, org, course, coursename): return render_to_response('temp-course-landing.html', {}) + @login_required @ensure_csrf_cookie def static_pages(request, org, course, coursename): @@ -921,7 +934,7 @@ def reorder_static_tabs(request): return HttpResponseBadRequest() # load all reference tabs, return BadRequest if we can't find any of them - tab_items =[] + tab_items = [] for tab in tabs: item = modulestore('direct').get_item(Location(tab)) if item is None: @@ -935,8 +948,8 @@ def reorder_static_tabs(request): for tab in course.tabs: if tab['type'] == 'static_tab': reordered_tabs.append({'type': 'static_tab', - 'name' : tab_items[static_tab_idx].metadata.get('display_name'), - 'url_slug' : tab_items[static_tab_idx].location.name}) + 'name': tab_items[static_tab_idx].metadata.get('display_name'), + 'url_slug': tab_items[static_tab_idx].location.name}) static_tab_idx += 1 else: reordered_tabs.append(tab) @@ -980,10 +993,11 @@ def edit_tabs(request, org, course, coursename): return render_to_response('edit-tabs.html', { 'active_tab': 'pages', - 'context_course':course_item, + 'context_course': course_item, 'components': components }) + def not_found(request): return render_to_response('error.html', {'error': '404'}) @@ -1014,11 +1028,12 @@ def course_info(request, org, course, name, provided_id=None): return render_to_response('course_info.html', { 'active_tab': 'courseinfo-tab', 'context_course': course_module, - 'url_base' : "/" + org + "/" + course + "/", - 'course_updates' : json.dumps(get_course_updates(location)), + 'url_base': "/" + org + "/" + course + "/", + 'course_updates': json.dumps(get_course_updates(location)), 'handouts_location': Location(['i4x', org, course, 'course_info', 'handouts']).url() }) + @expect_json @login_required @ensure_csrf_cookie @@ -1075,8 +1090,8 @@ def module_info(request, module_location): else: real_method = request.method - rewrite_static_links = request.GET.get('rewrite_url_links','True') in ['True', 'true'] - logging.debug('rewrite_static_links = {0} {1}'.format(request.GET.get('rewrite_url_links','False'), rewrite_static_links)) + rewrite_static_links = request.GET.get('rewrite_url_links', 'True') in ['True', 'true'] + logging.debug('rewrite_static_links = {0} {1}'.format(request.GET.get('rewrite_url_links', 'False'), rewrite_static_links)) # check that logged in user has permissions to this item if not has_access(request.user, location): @@ -1089,6 +1104,7 @@ def module_info(request, module_location): else: return HttpResponseBadRequest() + @login_required @ensure_csrf_cookie def get_course_settings(request, org, course, name): @@ -1109,9 +1125,10 @@ def get_course_settings(request, org, course, name): return render_to_response('settings.html', { 'active_tab': 'settings', 'context_course': course_module, - 'course_details' : json.dumps(course_details, cls=CourseSettingsEncoder) + 'course_details': json.dumps(course_details, cls=CourseSettingsEncoder) }) + @expect_json @login_required @ensure_csrf_cookie @@ -1137,12 +1154,13 @@ def course_settings_updates(request, org, course, name, section): if request.method == 'GET': # Cannot just do a get w/o knowing the course name :-( - return HttpResponse(json.dumps(manager.fetch(Location(['i4x', org, course, 'course',name])), cls=CourseSettingsEncoder), + return HttpResponse(json.dumps(manager.fetch(Location(['i4x', org, course, 'course', name])), cls=CourseSettingsEncoder), mimetype="application/json") - elif request.method == 'POST': # post or put, doesn't matter. + elif request.method == 'POST': # post or put, doesn't matter. return HttpResponse(json.dumps(manager.update_from_json(request.POST), cls=CourseSettingsEncoder), mimetype="application/json") + @expect_json @login_required @ensure_csrf_cookie @@ -1167,14 +1185,14 @@ def course_grader_updates(request, org, course, name, grader_index=None): if real_method == 'GET': # Cannot just do a get w/o knowing the course name :-( - return HttpResponse(json.dumps(CourseGradingModel.fetch_grader(Location(['i4x', org, course, 'course',name]), grader_index)), + return HttpResponse(json.dumps(CourseGradingModel.fetch_grader(Location(['i4x', org, course, 'course', name]), grader_index)), mimetype="application/json") elif real_method == "DELETE": # ??? Shoudl this return anything? Perhaps success fail? - CourseGradingModel.delete_grader(Location(['i4x', org, course, 'course',name]), grader_index) + CourseGradingModel.delete_grader(Location(['i4x', org, course, 'course', name]), grader_index) return HttpResponse() - elif request.method == 'POST': # post or put, doesn't matter. - return HttpResponse(json.dumps(CourseGradingModel.update_grader_from_json(Location(['i4x', org, course, 'course',name]), request.POST)), + elif request.method == 'POST': # post or put, doesn't matter. + return HttpResponse(json.dumps(CourseGradingModel.update_grader_from_json(Location(['i4x', org, course, 'course', name]), request.POST)), mimetype="application/json") @@ -1193,10 +1211,10 @@ def asset_index(request, org, course, name): raise PermissionDenied() - upload_asset_callback_url = reverse('upload_asset', kwargs = { - 'org' : org, - 'course' : course, - 'coursename' : name + upload_asset_callback_url = reverse('upload_asset', kwargs={ + 'org': org, + 'course': course, + 'coursename': name }) course_module = modulestore().get_item(location) @@ -1237,13 +1255,14 @@ def asset_index(request, org, course, name): def edge(request): return render_to_response('university_profiles/edge.html', {}) + @login_required @expect_json def create_new_course(request): # This logic is repeated in xmodule/modulestore/tests/factories.py # so if you change anything here, you need to also change it there. # TODO: write a test that creates two courses, one with the factory and - # the other with this method, then compare them to make sure they are + # the other with this method, then compare them to make sure they are # equivalent. template = Location(request.POST['template']) org = request.POST.get('org') @@ -1288,6 +1307,7 @@ def create_new_course(request): return HttpResponse(json.dumps({'id': new_course.location.url()})) + def initialize_course_tabs(course): # set up the default tabs # I've added this because when we add static tabs, the LMS either expects a None for the tabs list or @@ -1297,7 +1317,7 @@ def initialize_course_tabs(course): # This logic is repeated in xmodule/modulestore/tests/factories.py # so if you change anything here, you need to also change it there. - course.tabs = [{"type": "courseware"}, + course.tabs = [{"type": "courseware"}, {"type": "course_info", "name": "Course Info"}, {"type": "discussion", "name": "Discussion"}, {"type": "wiki", "name": "Wiki"}, @@ -1305,6 +1325,7 @@ def initialize_course_tabs(course): modulestore('direct').update_metadata(course.location.url(), course.own_metadata) + @ensure_csrf_cookie @login_required def import_course(request, org, course, name): @@ -1343,7 +1364,7 @@ def import_course(request, org, course, name): # find the 'course.xml' file - for r,d,f in os.walk(course_dir): + for r, d, f in os.walk(course_dir): for files in f: if files == 'course.xml': break @@ -1357,10 +1378,10 @@ def import_course(request, org, course, name): if r != course_dir: for fname in os.listdir(r): - shutil.move(r/fname, course_dir) + shutil.move(r / fname, course_dir) module_store, course_items = import_from_xml(modulestore('direct'), settings.GITHUB_REPO_ROOT, - [course_subdir], load_error_modules=False, static_content_store=contentstore(), target_location_namespace = Location(location)) + [course_subdir], load_error_modules=False, static_content_store=contentstore(), target_location_namespace=Location(location)) # we can blow this away when we're done importing. shutil.rmtree(course_dir) @@ -1376,12 +1397,13 @@ def import_course(request, org, course, name): return render_to_response('import.html', { 'context_course': course_module, 'active_tab': 'import', - 'successful_import_redirect_url' : reverse('course_index', args=[ + 'successful_import_redirect_url': reverse('course_index', args=[ course_module.location.org, course_module.location.course, course_module.location.name]) }) + @ensure_csrf_cookie @login_required def generate_export_course(request, org, course, name): @@ -1391,7 +1413,7 @@ def generate_export_course(request, org, course, name): raise PermissionDenied() loc = Location(location) - export_file = NamedTemporaryFile(prefix=name+'.', suffix=".tar.gz") + export_file = NamedTemporaryFile(prefix=name + '.', suffix=".tar.gz") root_dir = path(mkdtemp()) @@ -1404,11 +1426,11 @@ def generate_export_course(request, org, course, name): logging.debug('tar file being generated at {0}'.format(export_file.name)) tf = tarfile.open(name=export_file.name, mode='w:gz') - tf.add(root_dir/name, arcname=name) + tf.add(root_dir / name, arcname=name) tf.close() # remove temp dir - shutil.rmtree(root_dir/name) + shutil.rmtree(root_dir / name) wrapper = FileWrapper(export_file) response = HttpResponse(wrapper, content_type='application/x-tgz') @@ -1430,9 +1452,10 @@ def export_course(request, org, course, name): return render_to_response('export.html', { 'context_course': course_module, 'active_tab': 'export', - 'successful_import_redirect_url' : '' + 'successful_import_redirect_url': '' }) + def event(request): ''' A noop to swallow the analytics call so that cms methods don't spook and poor developers looking at diff --git a/cms/djangoapps/models/settings/course_details.py b/cms/djangoapps/models/settings/course_details.py index d01e784d74..b27f4e3804 100644 --- a/cms/djangoapps/models/settings/course_details.py +++ b/cms/djangoapps/models/settings/course_details.py @@ -31,16 +31,16 @@ class CourseDetails(object): """ if not isinstance(course_location, Location): course_location = Location(course_location) - + course = cls(course_location) - + descriptor = get_modulestore(course_location).get_item(course_location) - + course.start_date = descriptor.start course.end_date = descriptor.end course.enrollment_start = descriptor.enrollment_start course.enrollment_end = descriptor.enrollment_end - + temploc = course_location._replace(category='about', name='syllabus') try: course.syllabus = get_modulestore(temploc).get_item(temploc).definition['data'] @@ -52,32 +52,32 @@ class CourseDetails(object): course.overview = get_modulestore(temploc).get_item(temploc).definition['data'] except ItemNotFoundError: pass - + temploc = temploc._replace(name='effort') try: course.effort = get_modulestore(temploc).get_item(temploc).definition['data'] except ItemNotFoundError: pass - + temploc = temploc._replace(name='video') try: raw_video = get_modulestore(temploc).get_item(temploc).definition['data'] - course.intro_video = CourseDetails.parse_video_tag(raw_video) + course.intro_video = CourseDetails.parse_video_tag(raw_video) except ItemNotFoundError: pass - + return course - + @classmethod def update_from_json(cls, jsondict): """ Decode the json into CourseDetails and save any changed attrs to the db """ - ## TODO make it an error for this to be undefined & for it to not be retrievable from modulestore + ## TODO make it an error for this to be undefined & for it to not be retrievable from modulestore course_location = jsondict['course_location'] ## Will probably want to cache the inflight courses because every blur generates an update descriptor = get_modulestore(course_location).get_item(course_location) - + dirty = False if 'start_date' in jsondict: @@ -87,7 +87,7 @@ class CourseDetails(object): if converted != descriptor.start: dirty = True descriptor.start = converted - + if 'end_date' in jsondict: converted = jsdate_to_time(jsondict['end_date']) else: @@ -96,7 +96,7 @@ class CourseDetails(object): if converted != descriptor.end: dirty = True descriptor.end = converted - + if 'enrollment_start' in jsondict: converted = jsdate_to_time(jsondict['enrollment_start']) else: @@ -105,7 +105,7 @@ class CourseDetails(object): if converted != descriptor.enrollment_start: dirty = True descriptor.enrollment_start = converted - + if 'enrollment_end' in jsondict: converted = jsdate_to_time(jsondict['enrollment_end']) else: @@ -114,10 +114,10 @@ class CourseDetails(object): if converted != descriptor.enrollment_end: dirty = True descriptor.enrollment_end = converted - + if dirty: get_modulestore(course_location).update_metadata(course_location, descriptor.metadata) - + # NOTE: below auto writes to the db w/o verifying that any of the fields actually changed # to make faster, could compare against db or could have client send over a list of which fields changed. temploc = Location(course_location)._replace(category='about', name='syllabus') @@ -125,19 +125,19 @@ class CourseDetails(object): temploc = temploc._replace(name='overview') update_item(temploc, jsondict['overview']) - + temploc = temploc._replace(name='effort') update_item(temploc, jsondict['effort']) - + temploc = temploc._replace(name='video') recomposed_video_tag = CourseDetails.recompose_video_tag(jsondict['intro_video']) update_item(temploc, recomposed_video_tag) - - + + # Could just generate and return a course obj w/o doing any db reads, but I put the reads in as a means to confirm # it persisted correctly return CourseDetails.fetch(course_location) - + @staticmethod def parse_video_tag(raw_video): """ @@ -147,17 +147,17 @@ class CourseDetails(object): """ if not raw_video: return None - + keystring_matcher = re.search('(?<=embed/)[a-zA-Z0-9_-]+', raw_video) if keystring_matcher is None: keystring_matcher = re.search('' return result - + # TODO move to a more general util? Is there a better way to do the isinstance model check? class CourseSettingsEncoder(json.JSONEncoder): diff --git a/cms/djangoapps/models/settings/course_grading.py b/cms/djangoapps/models/settings/course_grading.py index 9cfa18c8c9..f4c6fd3d7c 100644 --- a/cms/djangoapps/models/settings/course_grading.py +++ b/cms/djangoapps/models/settings/course_grading.py @@ -6,55 +6,55 @@ from util import converters class CourseGradingModel(object): """ - Basically a DAO and Model combo for CRUD operations pertaining to grading policy. + Basically a DAO and Model combo for CRUD operations pertaining to grading policy. """ def __init__(self, course_descriptor): self.course_location = course_descriptor.location - self.graders = [CourseGradingModel.jsonize_grader(i, grader) for i, grader in enumerate(course_descriptor.raw_grader)] # weights transformed to ints [0..100] + self.graders = [CourseGradingModel.jsonize_grader(i, grader) for i, grader in enumerate(course_descriptor.raw_grader)] # weights transformed to ints [0..100] self.grade_cutoffs = course_descriptor.grade_cutoffs self.grace_period = CourseGradingModel.convert_set_grace_period(course_descriptor) - - @classmethod + + @classmethod def fetch(cls, course_location): """ Fetch the course details for the given course from persistence and return a CourseDetails model. """ if not isinstance(course_location, Location): course_location = Location(course_location) - + descriptor = get_modulestore(course_location).get_item(course_location) model = cls(descriptor) return model - + @staticmethod def fetch_grader(course_location, index): """ - Fetch the course's nth grader + Fetch the course's nth grader Returns an empty dict if there's no such grader. """ if not isinstance(course_location, Location): course_location = Location(course_location) - + descriptor = get_modulestore(course_location).get_item(course_location) # # ??? it would be good if these had the course_location in them so that they stand alone sufficiently # # but that would require not using CourseDescriptor's field directly. Opinions? - index = int(index) - if len(descriptor.raw_grader) > index: + index = int(index) + if len(descriptor.raw_grader) > index: return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index]) - + # return empty model else: return { - "id" : index, - "type" : "", - "min_count" : 0, - "drop_count" : 0, - "short_label" : None, - "weight" : 0 + "id": index, + "type": "", + "min_count": 0, + "drop_count": 0, + "short_label": None, + "weight": 0 } - + @staticmethod def fetch_cutoffs(course_location): """ @@ -62,7 +62,7 @@ class CourseGradingModel(object): """ if not isinstance(course_location, Location): course_location = Location(course_location) - + descriptor = get_modulestore(course_location).get_item(course_location) return descriptor.grade_cutoffs @@ -73,10 +73,10 @@ class CourseGradingModel(object): """ if not isinstance(course_location, Location): course_location = Location(course_location) - + descriptor = get_modulestore(course_location).get_item(course_location) - return {'grace_period' : CourseGradingModel.convert_set_grace_period(descriptor) } - + return {'grace_period': CourseGradingModel.convert_set_grace_period(descriptor)} + @staticmethod def update_from_json(jsondict): """ @@ -85,32 +85,32 @@ class CourseGradingModel(object): """ course_location = jsondict['course_location'] descriptor = get_modulestore(course_location).get_item(course_location) - + graders_parsed = [CourseGradingModel.parse_grader(jsonele) for jsonele in jsondict['graders']] - + descriptor.raw_grader = graders_parsed descriptor.grade_cutoffs = jsondict['grade_cutoffs'] - + get_modulestore(course_location).update_item(course_location, descriptor.definition['data']) CourseGradingModel.update_grace_period_from_json(course_location, jsondict['grace_period']) - + return CourseGradingModel.fetch(course_location) - - + + @staticmethod def update_grader_from_json(course_location, grader): """ - Create or update the grader of the given type (string key) for the given course. Returns the modified + Create or update the grader of the given type (string key) for the given course. Returns the modified grader which is a full model on the client but not on the server (just a dict) """ if not isinstance(course_location, Location): course_location = Location(course_location) - + descriptor = get_modulestore(course_location).get_item(course_location) # # ??? it would be good if these had the course_location in them so that they stand alone sufficiently # # but that would require not using CourseDescriptor's field directly. Opinions? - # parse removes the id; so, grab it before parse + # parse removes the id; so, grab it before parse index = int(grader.get('id', len(descriptor.raw_grader))) grader = CourseGradingModel.parse_grader(grader) @@ -118,11 +118,11 @@ class CourseGradingModel(object): descriptor.raw_grader[index] = grader else: descriptor.raw_grader.append(grader) - + get_modulestore(course_location).update_item(course_location, descriptor.definition['data']) - + return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index]) - + @staticmethod def update_cutoffs_from_json(course_location, cutoffs): """ @@ -131,18 +131,18 @@ class CourseGradingModel(object): """ if not isinstance(course_location, Location): course_location = Location(course_location) - + descriptor = get_modulestore(course_location).get_item(course_location) descriptor.grade_cutoffs = cutoffs get_modulestore(course_location).update_item(course_location, descriptor.definition['data']) - + return cutoffs - - + + @staticmethod def update_grace_period_from_json(course_location, graceperiodjson): """ - Update the course's default grace period. Incoming dict is {hours: h, minutes: m} possibly as a + Update the course's default grace period. Incoming dict is {hours: h, minutes: m} possibly as a grace_period entry in an enclosing dict. It is also safe to call this method with a value of None for graceperiodjson. """ @@ -160,7 +160,7 @@ class CourseGradingModel(object): descriptor = get_modulestore(course_location).get_item(course_location) descriptor.metadata['graceperiod'] = grace_rep get_modulestore(course_location).update_metadata(course_location, descriptor.metadata) - + @staticmethod def delete_grader(course_location, index): """ @@ -168,16 +168,16 @@ class CourseGradingModel(object): """ if not isinstance(course_location, Location): course_location = Location(course_location) - + descriptor = get_modulestore(course_location).get_item(course_location) - index = int(index) + index = int(index) if index < len(descriptor.raw_grader): del descriptor.raw_grader[index] # force propagation to definition descriptor.raw_grader = descriptor.raw_grader get_modulestore(course_location).update_item(course_location, descriptor.definition['data']) - - # NOTE cannot delete cutoffs. May be useful to reset + + # NOTE cannot delete cutoffs. May be useful to reset @staticmethod def delete_cutoffs(course_location, cutoffs): """ @@ -185,13 +185,13 @@ class CourseGradingModel(object): """ if not isinstance(course_location, Location): course_location = Location(course_location) - + descriptor = get_modulestore(course_location).get_item(course_location) descriptor.grade_cutoffs = descriptor.defaut_grading_policy['GRADE_CUTOFFS'] get_modulestore(course_location).update_item(course_location, descriptor.definition['data']) - + return descriptor.grade_cutoffs - + @staticmethod def delete_grace_period(course_location): """ @@ -199,28 +199,28 @@ class CourseGradingModel(object): """ if not isinstance(course_location, Location): course_location = Location(course_location) - + descriptor = get_modulestore(course_location).get_item(course_location) if 'graceperiod' in descriptor.metadata: del descriptor.metadata['graceperiod'] get_modulestore(course_location).update_metadata(course_location, descriptor.metadata) - + @staticmethod def get_section_grader_type(location): if not isinstance(location, Location): location = Location(location) - + descriptor = get_modulestore(location).get_item(location) return { - "graderType" : descriptor.metadata.get('format', u"Not Graded"), - "location" : location, - "id" : 99 # just an arbitrary value to + "graderType": descriptor.metadata.get('format', u"Not Graded"), + "location": location, + "id": 99 # just an arbitrary value to } - + @staticmethod def update_section_grader_type(location, jsondict): if not isinstance(location, Location): location = Location(location) - + descriptor = get_modulestore(location).get_item(location) if 'graderType' in jsondict and jsondict['graderType'] != u"Not Graded": descriptor.metadata['format'] = jsondict.get('graderType') @@ -228,10 +228,10 @@ class CourseGradingModel(object): else: if 'format' in descriptor.metadata: del descriptor.metadata['format'] if 'graded' in descriptor.metadata: del descriptor.metadata['graded'] - - get_modulestore(location).update_metadata(location, descriptor.metadata) - - + + get_modulestore(location).update_metadata(location, descriptor.metadata) + + @staticmethod def convert_set_grace_period(descriptor): # 5 hours 59 minutes 59 seconds => converted to iso format @@ -245,13 +245,13 @@ class CourseGradingModel(object): def parse_grader(json_grader): # manual to clear out kruft result = { - "type" : json_grader["type"], - "min_count" : int(json_grader.get('min_count', 0)), - "drop_count" : int(json_grader.get('drop_count', 0)), - "short_label" : json_grader.get('short_label', None), - "weight" : float(json_grader.get('weight', 0)) / 100.0 + "type": json_grader["type"], + "min_count": int(json_grader.get('min_count', 0)), + "drop_count": int(json_grader.get('drop_count', 0)), + "short_label": json_grader.get('short_label', None), + "weight": float(json_grader.get('weight', 0)) / 100.0 } - + return result @staticmethod @@ -260,6 +260,6 @@ class CourseGradingModel(object): if grader['weight']: grader['weight'] *= 100 if not 'short_label' in grader: - grader['short_label'] = "" - + grader['short_label'] = "" + return grader diff --git a/cms/envs/acceptance.py b/cms/envs/acceptance.py index 5bc9b53fc4..26a8adc92c 100644 --- a/cms/envs/acceptance.py +++ b/cms/envs/acceptance.py @@ -1,5 +1,5 @@ """ -This config file extends the test environment configuration +This config file extends the test environment configuration so that we can run the lettuce acceptance tests. """ from .test import * @@ -21,14 +21,14 @@ DATA_DIR = COURSES_ROOT # } # } -# Set this up so that rake lms[acceptance] and running the +# Set this up so that rake lms[acceptance] and running the # harvest command both use the same (test) database # which they can flush without messing up your dev db DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ENV_ROOT / "db" / "test_mitx.db", - 'TEST_NAME': ENV_ROOT / "db" / "test_mitx.db", + 'TEST_NAME': ENV_ROOT / "db" / "test_mitx.db", } } diff --git a/cms/envs/common.py b/cms/envs/common.py index 3ea532d70d..ef7a4f43fa 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -33,8 +33,8 @@ MITX_FEATURES = { 'USE_DJANGO_PIPELINE': True, 'GITHUB_PUSH': False, 'ENABLE_DISCUSSION_SERVICE': False, - 'AUTH_USE_MIT_CERTIFICATES' : False, - 'STUB_VIDEO_FOR_TESTING': False, # do not display video when running automated acceptance tests + 'AUTH_USE_MIT_CERTIFICATES': False, + 'STUB_VIDEO_FOR_TESTING': False, # do not display video when running automated acceptance tests } ENABLE_JASMINE = False @@ -229,7 +229,7 @@ PIPELINE_JS = { 'source_filenames': sorted( rooted_glob(COMMON_ROOT / 'static/', 'coffee/src/**/*.coffee') + rooted_glob(PROJECT_ROOT / 'static/', 'coffee/src/**/*.coffee') - ) + [ 'js/hesitate.js', 'js/base.js'], + ) + ['js/hesitate.js', 'js/base.js'], 'output_filename': 'js/cms-application.js', }, 'module-js': { diff --git a/cms/envs/dev.py b/cms/envs/dev.py index e29ee62e20..3dee93a398 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -12,7 +12,7 @@ TEMPLATE_DEBUG = DEBUG LOGGING = get_logger_config(ENV_ROOT / "log", logging_env="dev", tracking_filename="tracking.log", - dev_env = True, + dev_env=True, debug=True) modulestore_options = { @@ -41,7 +41,7 @@ CONTENTSTORE = { 'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore', 'OPTIONS': { 'host': 'localhost', - 'db' : 'xcontent', + 'db': 'xcontent', } } diff --git a/cms/envs/dev_ike.py b/cms/envs/dev_ike.py index 5fb120854b..1ebf219d44 100644 --- a/cms/envs/dev_ike.py +++ b/cms/envs/dev_ike.py @@ -9,8 +9,6 @@ import socket MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True -MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss - -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # django 1.4 for nginx ssl proxy - +MITX_FEATURES['USE_DJANGO_PIPELINE'] = False # don't recompile scss +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # django 1.4 for nginx ssl proxy diff --git a/cms/envs/test.py b/cms/envs/test.py index 74c3e349a4..7f39e6818b 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -19,7 +19,7 @@ TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' TEST_ROOT = path('test_root') # Makes the tests run much faster... -SOUTH_TESTS_MIGRATE = False # To disable migrations and use syncdb instead +SOUTH_TESTS_MIGRATE = False # To disable migrations and use syncdb instead # Want static files in the same dir for running on jenkins. STATIC_ROOT = TEST_ROOT / "staticfiles" @@ -62,7 +62,7 @@ CONTENTSTORE = { 'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore', 'OPTIONS': { 'host': 'localhost', - 'db' : 'xcontent', + 'db': 'xcontent', } } @@ -76,7 +76,7 @@ DATABASES = { LMS_BASE = "localhost:8000" CACHES = { - # This is the cache used for most things. Askbot will not work without a + # This is the cache used for most things. Askbot will not work without a # functioning cache -- it relies on caching to load its settings in places. # In staging/prod envs, the sessions also live here. 'default': { @@ -103,4 +103,4 @@ CACHES = { PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.SHA1PasswordHasher', 'django.contrib.auth.hashers.MD5PasswordHasher', -) \ No newline at end of file +) diff --git a/cms/manage.py b/cms/manage.py index f8773c0641..723fa59da1 100644 --- a/cms/manage.py +++ b/cms/manage.py @@ -2,7 +2,7 @@ from django.core.management import execute_manager import imp try: - imp.find_module('settings') # Assumed to be in the same directory. + imp.find_module('settings') # Assumed to be in the same directory. except ImportError: import sys sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. " diff --git a/cms/urls.py b/cms/urls.py index c928e74d19..ad4dd87d74 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -48,7 +48,7 @@ urlpatterns = ('', url(r'^(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/gradeas.*$', 'contentstore.views.assignment_type_update', name='assignment_type_update'), - url(r'^pages/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.static_pages', + url(r'^pages/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.static_pages', name='static_pages'), url(r'^edit_static/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.edit_static', name='edit_static'), url(r'^edit_tabs/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.edit_tabs', name='edit_tabs'), @@ -56,7 +56,7 @@ urlpatterns = ('', # this is a generic method to return the data/metadata associated with a xmodule url(r'^module_info/(?P.*)$', 'contentstore.views.module_info', name='module_info'), - + # temporary landing page for a course url(r'^edge/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.landing', name='landing'), diff --git a/common/djangoapps/cache_toolbox/core.py b/common/djangoapps/cache_toolbox/core.py index 3bd184bc2c..a9c7002aa6 100644 --- a/common/djangoapps/cache_toolbox/core.py +++ b/common/djangoapps/cache_toolbox/core.py @@ -14,6 +14,7 @@ from django.db import DEFAULT_DB_ALIAS from . import app_settings from xmodule.contentstore.content import StaticContent + def get_instance(model, instance_or_pk, timeout=None, using=None): """ Returns the ``model`` instance with a primary key of ``instance_or_pk``. @@ -108,11 +109,14 @@ def instance_key(model, instance_or_pk): getattr(instance_or_pk, 'pk', instance_or_pk), ) + def set_cached_content(content): cache.set(str(content.location), content) + def get_cached_content(location): return cache.get(str(location)) + def del_cached_content(location): cache.delete(str(location)) diff --git a/common/djangoapps/contentserver/middleware.py b/common/djangoapps/contentserver/middleware.py index 1d139bcaa0..c5e887801e 100644 --- a/common/djangoapps/contentserver/middleware.py +++ b/common/djangoapps/contentserver/middleware.py @@ -12,7 +12,7 @@ from xmodule.exceptions import NotFoundError class StaticContentServer(object): def process_request(self, request): # look to see if the request is prefixed with 'c4x' tag - if request.path.startswith('/' + XASSET_LOCATION_TAG +'/'): + if request.path.startswith('/' + XASSET_LOCATION_TAG + '/'): loc = StaticContent.get_location_from_path(request.path) # first look in our cache so we don't have to round-trip to the DB content = get_cached_content(loc) diff --git a/common/djangoapps/course_groups/cohorts.py b/common/djangoapps/course_groups/cohorts.py index f84e18b214..155f82e0c7 100644 --- a/common/djangoapps/course_groups/cohorts.py +++ b/common/djangoapps/course_groups/cohorts.py @@ -13,6 +13,7 @@ from .models import CourseUserGroup log = logging.getLogger(__name__) + def is_course_cohorted(course_id): """ Given a course id, return a boolean for whether or not the course is @@ -115,6 +116,7 @@ def get_course_cohorts(course_id): ### Helpers for cohort management views + def get_cohort_by_name(course_id, name): """ Return the CourseUserGroup object for the given cohort. Raises DoesNotExist @@ -124,6 +126,7 @@ def get_cohort_by_name(course_id, name): group_type=CourseUserGroup.COHORT, name=name) + def get_cohort_by_id(course_id, cohort_id): """ Return the CourseUserGroup object for the given cohort. Raises DoesNotExist @@ -133,6 +136,7 @@ def get_cohort_by_id(course_id, cohort_id): group_type=CourseUserGroup.COHORT, id=cohort_id) + def add_cohort(course_id, name): """ Add a cohort to a course. Raises ValueError if a cohort of the same name already @@ -148,12 +152,14 @@ def add_cohort(course_id, name): group_type=CourseUserGroup.COHORT, name=name) + class CohortConflict(Exception): """ Raised when user to be added is already in another cohort in same course. """ pass + def add_user_to_cohort(cohort, username_or_email): """ Look up the given user, and if successful, add them to the specified cohort. @@ -211,4 +217,3 @@ def delete_empty_cohort(course_id, name): name, course_id)) cohort.delete() - diff --git a/common/djangoapps/course_groups/models.py b/common/djangoapps/course_groups/models.py index 957d230d92..8bab17493b 100644 --- a/common/djangoapps/course_groups/models.py +++ b/common/djangoapps/course_groups/models.py @@ -5,6 +5,7 @@ from django.db import models log = logging.getLogger(__name__) + class CourseUserGroup(models.Model): """ This model represents groups of users in a course. Groups may have different types, @@ -30,5 +31,3 @@ class CourseUserGroup(models.Model): COHORT = 'cohort' GROUP_TYPE_CHOICES = ((COHORT, 'Cohort'),) group_type = models.CharField(max_length=20, choices=GROUP_TYPE_CHOICES) - - diff --git a/common/djangoapps/course_groups/tests/tests.py b/common/djangoapps/course_groups/tests/tests.py index 86f0be0791..0fbf863fee 100644 --- a/common/djangoapps/course_groups/tests/tests.py +++ b/common/djangoapps/course_groups/tests/tests.py @@ -12,7 +12,8 @@ from xmodule.modulestore.django import modulestore, _MODULESTORES # NOTE: running this with the lms.envs.test config works without # manually overriding the modulestore. However, running with -# cms.envs.test doesn't. +# cms.envs.test doesn't. + def xml_store_config(data_dir): return { @@ -28,6 +29,7 @@ def xml_store_config(data_dir): TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) + @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class TestCohorts(django.test.TestCase): @@ -184,6 +186,3 @@ class TestCohorts(django.test.TestCase): self.assertTrue( is_commentable_cohorted(course.id, to_id("Feedback")), "Feedback was listed as cohorted. Should be.") - - - diff --git a/common/djangoapps/course_groups/views.py b/common/djangoapps/course_groups/views.py index d591c44356..6d5ac43fb0 100644 --- a/common/djangoapps/course_groups/views.py +++ b/common/djangoapps/course_groups/views.py @@ -22,6 +22,7 @@ import track.views log = logging.getLogger(__name__) + def json_http_response(data): """ Return an HttpResponse with the data json-serialized and the right content @@ -29,6 +30,7 @@ def json_http_response(data): """ return HttpResponse(json.dumps(data), content_type="application/json") + def split_by_comma_and_whitespace(s): """ Split a string both by commas and whitespice. Returns a list. @@ -177,6 +179,7 @@ def add_users_to_cohort(request, course_id, cohort_id): 'conflict': conflict, 'unknown': unknown}) + @ensure_csrf_cookie @require_POST def remove_user_from_cohort(request, course_id, cohort_id): diff --git a/common/djangoapps/external_auth/admin.py b/common/djangoapps/external_auth/admin.py index e93325bcb2..1ee18dadc1 100644 --- a/common/djangoapps/external_auth/admin.py +++ b/common/djangoapps/external_auth/admin.py @@ -5,8 +5,9 @@ django admin pages for courseware model from external_auth.models import * from django.contrib import admin + class ExternalAuthMapAdmin(admin.ModelAdmin): - search_fields = ['external_id','user__username'] + search_fields = ['external_id', 'user__username'] date_hierarchy = 'dtcreated' admin.site.register(ExternalAuthMap, ExternalAuthMapAdmin) diff --git a/common/djangoapps/external_auth/models.py b/common/djangoapps/external_auth/models.py index e43b306bbb..6c2f38d8b3 100644 --- a/common/djangoapps/external_auth/models.py +++ b/common/djangoapps/external_auth/models.py @@ -12,20 +12,20 @@ file and check it in at the same time as your model changes. To do that, from django.db import models from django.contrib.auth.models import User + class ExternalAuthMap(models.Model): class Meta: unique_together = (('external_id', 'external_domain'), ) external_id = models.CharField(max_length=255, db_index=True) external_domain = models.CharField(max_length=255, db_index=True) - external_credentials = models.TextField(blank=True) # JSON dictionary + external_credentials = models.TextField(blank=True) # JSON dictionary external_email = models.CharField(max_length=255, db_index=True) - external_name = models.CharField(blank=True,max_length=255, db_index=True) + external_name = models.CharField(blank=True, max_length=255, db_index=True) user = models.OneToOneField(User, unique=True, db_index=True, null=True) - internal_password = models.CharField(blank=True, max_length=31) # randomly generated - dtcreated = models.DateTimeField('creation date',auto_now_add=True) - dtsignup = models.DateTimeField('signup date',null=True) # set after signup - + internal_password = models.CharField(blank=True, max_length=31) # randomly generated + dtcreated = models.DateTimeField('creation date', auto_now_add=True) + dtsignup = models.DateTimeField('signup date', null=True) # set after signup + def __unicode__(self): s = "[%s] = (%s / %s)" % (self.external_id, self.external_name, self.external_email) return s - diff --git a/common/djangoapps/external_auth/tests/test_openid_provider.py b/common/djangoapps/external_auth/tests/test_openid_provider.py index 9c522f88b4..570dfbf9ee 100644 --- a/common/djangoapps/external_auth/tests/test_openid_provider.py +++ b/common/djangoapps/external_auth/tests/test_openid_provider.py @@ -13,9 +13,10 @@ from django.test import TestCase, LiveServerTestCase from django.core.urlresolvers import reverse from django.test.client import RequestFactory + class MyFetcher(HTTPFetcher): """A fetcher that uses server-internal calls for performing HTTP - requests. + requests. """ def __init__(self, client): @@ -42,7 +43,7 @@ class MyFetcher(HTTPFetcher): if headers and 'Accept' in headers: data['CONTENT_TYPE'] = headers['Accept'] response = self.client.get(url, data) - + # Translate the test client response to the fetcher's HTTP response abstraction content = response.content final_url = url @@ -60,6 +61,7 @@ class MyFetcher(HTTPFetcher): status=status, ) + class OpenIdProviderTest(TestCase): # def setUp(self): @@ -78,7 +80,7 @@ class OpenIdProviderTest(TestCase): provider_url = reverse('openid-provider-xrds') factory = RequestFactory() request = factory.request() - abs_provider_url = request.build_absolute_uri(location = provider_url) + abs_provider_url = request.build_absolute_uri(location=provider_url) # In order for this absolute URL to work (i.e. to get xrds, then authentication) # in the test environment, we either need a live server that works with the default @@ -86,10 +88,10 @@ class OpenIdProviderTest(TestCase): # Here we do the latter: fetcher = MyFetcher(self.client) openid.fetchers.setDefaultFetcher(fetcher, wrap_exceptions=False) - + # now we can begin the login process by invoking a local openid client, # with a pointer to the (also-local) openid provider: - with self.settings(OPENID_SSO_SERVER_URL = abs_provider_url): + with self.settings(OPENID_SSO_SERVER_URL=abs_provider_url): url = reverse('openid-login') resp = self.client.post(url) code = 200 @@ -107,7 +109,7 @@ class OpenIdProviderTest(TestCase): provider_url = reverse('openid-provider-login') factory = RequestFactory() request = factory.request() - abs_provider_url = request.build_absolute_uri(location = provider_url) + abs_provider_url = request.build_absolute_uri(location=provider_url) # In order for this absolute URL to work (i.e. to get xrds, then authentication) # in the test environment, we either need a live server that works with the default @@ -115,10 +117,10 @@ class OpenIdProviderTest(TestCase): # Here we do the latter: fetcher = MyFetcher(self.client) openid.fetchers.setDefaultFetcher(fetcher, wrap_exceptions=False) - + # now we can begin the login process by invoking a local openid client, # with a pointer to the (also-local) openid provider: - with self.settings(OPENID_SSO_SERVER_URL = abs_provider_url): + with self.settings(OPENID_SSO_SERVER_URL=abs_provider_url): url = reverse('openid-login') resp = self.client.post(url) code = 200 @@ -143,43 +145,43 @@ class OpenIdProviderTest(TestCase): self.assertContains(resp, '', html=True) # this should work on the server: self.assertContains(resp, '', html=True) - + # not included here are elements that will vary from run to run: # # - - + + def testOpenIdSetup(self): if not settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'): return url = reverse('openid-provider-login') post_args = { - "openid.mode" : "checkid_setup", - "openid.return_to" : "http://testserver/openid/complete/?janrain_nonce=2013-01-23T06%3A20%3A17ZaN7j6H", - "openid.assoc_handle" : "{HMAC-SHA1}{50ff8120}{rh87+Q==}", - "openid.claimed_id" : "http://specs.openid.net/auth/2.0/identifier_select", - "openid.ns" : "http://specs.openid.net/auth/2.0", - "openid.realm" : "http://testserver/", - "openid.identity" : "http://specs.openid.net/auth/2.0/identifier_select", - "openid.ns.ax" : "http://openid.net/srv/ax/1.0", - "openid.ax.mode" : "fetch_request", - "openid.ax.required" : "email,fullname,old_email,firstname,old_nickname,lastname,old_fullname,nickname", - "openid.ax.type.fullname" : "http://axschema.org/namePerson", - "openid.ax.type.lastname" : "http://axschema.org/namePerson/last", - "openid.ax.type.firstname" : "http://axschema.org/namePerson/first", - "openid.ax.type.nickname" : "http://axschema.org/namePerson/friendly", - "openid.ax.type.email" : "http://axschema.org/contact/email", - "openid.ax.type.old_email" : "http://schema.openid.net/contact/email", - "openid.ax.type.old_nickname" : "http://schema.openid.net/namePerson/friendly", - "openid.ax.type.old_fullname" : "http://schema.openid.net/namePerson", + "openid.mode": "checkid_setup", + "openid.return_to": "http://testserver/openid/complete/?janrain_nonce=2013-01-23T06%3A20%3A17ZaN7j6H", + "openid.assoc_handle": "{HMAC-SHA1}{50ff8120}{rh87+Q==}", + "openid.claimed_id": "http://specs.openid.net/auth/2.0/identifier_select", + "openid.ns": "http://specs.openid.net/auth/2.0", + "openid.realm": "http://testserver/", + "openid.identity": "http://specs.openid.net/auth/2.0/identifier_select", + "openid.ns.ax": "http://openid.net/srv/ax/1.0", + "openid.ax.mode": "fetch_request", + "openid.ax.required": "email,fullname,old_email,firstname,old_nickname,lastname,old_fullname,nickname", + "openid.ax.type.fullname": "http://axschema.org/namePerson", + "openid.ax.type.lastname": "http://axschema.org/namePerson/last", + "openid.ax.type.firstname": "http://axschema.org/namePerson/first", + "openid.ax.type.nickname": "http://axschema.org/namePerson/friendly", + "openid.ax.type.email": "http://axschema.org/contact/email", + "openid.ax.type.old_email": "http://schema.openid.net/contact/email", + "openid.ax.type.old_nickname": "http://schema.openid.net/namePerson/friendly", + "openid.ax.type.old_fullname": "http://schema.openid.net/namePerson", } resp = self.client.post(url, post_args) code = 200 self.assertEqual(resp.status_code, code, "got code {0} for url '{1}'. Expected code {2}" .format(resp.status_code, url, code)) - - + + # In order for this absolute URL to work (i.e. to get xrds, then authentication) # in the test environment, we either need a live server that works with the default # fetcher (i.e. urlopen2), or a test server that is reached through a custom fetcher. @@ -196,11 +198,11 @@ class OpenIdProviderLiveServerTest(LiveServerTestCase): provider_url = reverse('openid-provider-xrds') factory = RequestFactory() request = factory.request() - abs_provider_url = request.build_absolute_uri(location = provider_url) + abs_provider_url = request.build_absolute_uri(location=provider_url) # now we can begin the login process by invoking a local openid client, # with a pointer to the (also-local) openid provider: - with self.settings(OPENID_SSO_SERVER_URL = abs_provider_url): + with self.settings(OPENID_SSO_SERVER_URL=abs_provider_url): url = reverse('openid-login') resp = self.client.post(url) code = 200 diff --git a/common/djangoapps/external_auth/views.py b/common/djangoapps/external_auth/views.py index d557b33e9c..effae923b3 100644 --- a/common/djangoapps/external_auth/views.py +++ b/common/djangoapps/external_auth/views.py @@ -215,7 +215,7 @@ def ssl_dn_extract_info(dn): else: return None return (user, email, fullname) - + def ssl_get_cert_from_request(request): """ @@ -460,7 +460,7 @@ def provider_login(request): openid_request.answer(False), {}) # checkid_setup, so display login page - # (by falling through to the provider_login at the + # (by falling through to the provider_login at the # bottom of this method). elif openid_request.mode == 'checkid_setup': if openid_request.idSelect(): @@ -482,7 +482,7 @@ def provider_login(request): # handle login redirection: these are also sent to this view function, # but are distinguished by lacking the openid mode. We also know that - # they are posts, because they come from the popup + # they are posts, because they come from the popup elif request.method == 'POST' and 'openid_setup' in request.session: # get OpenID request from session openid_setup = request.session['openid_setup'] @@ -495,7 +495,7 @@ def provider_login(request): return default_render_failure(request, "Invalid OpenID trust root") # check if user with given email exists - # Failure is redirected to this method (by using the original URL), + # Failure is redirected to this method (by using the original URL), # which will bring up the login dialog. email = request.POST.get('email', None) try: @@ -542,17 +542,17 @@ def provider_login(request): # missing fields is up to the Consumer. The proper change # should only return the username, however this will likely # break the CS50 client. Temporarily we will be returning - # username filling in for fullname in addition to username + # username filling in for fullname in addition to username # as sreg nickname. - - # Note too that this is hardcoded, and not really responding to + + # Note too that this is hardcoded, and not really responding to # the extensions that were registered in the first place. results = { 'nickname': user.username, 'email': user.email, 'fullname': user.username } - + # the request succeeded: return provider_respond(server, openid_request, response, results) diff --git a/common/djangoapps/mitxmako/makoloader.py b/common/djangoapps/mitxmako/makoloader.py index 1379027e07..29184299b6 100644 --- a/common/djangoapps/mitxmako/makoloader.py +++ b/common/djangoapps/mitxmako/makoloader.py @@ -12,34 +12,35 @@ import mitxmako.middleware log = logging.getLogger(__name__) + class MakoLoader(object): """ This is a Django loader object which will load the template as a Mako template if the first line is "## mako". It is based off BaseLoader in django.template.loader. """ - + is_usable = False def __init__(self, base_loader): # base_loader is an instance of a BaseLoader subclass self.base_loader = base_loader - + module_directory = getattr(settings, 'MAKO_MODULE_DIR', None) - + if module_directory is None: log.warning("For more caching of mako templates, set the MAKO_MODULE_DIR in settings!") module_directory = tempfile.mkdtemp() - + self.module_directory = module_directory - - + + def __call__(self, template_name, template_dirs=None): return self.load_template(template_name, template_dirs) def load_template(self, template_name, template_dirs=None): source, file_path = self.load_template_source(template_name, template_dirs) - + if source.startswith("## mako\n"): # This is a mako template template = Template(filename=file_path, module_directory=self.module_directory, uri=template_name) @@ -56,23 +57,24 @@ class MakoLoader(object): # This allows for correct identification (later) of the actual template that does # not exist. return source, file_path - + def load_template_source(self, template_name, template_dirs=None): # Just having this makes the template load as an instance, instead of a class. return self.base_loader.load_template_source(template_name, template_dirs) def reset(self): self.base_loader.reset() - + class MakoFilesystemLoader(MakoLoader): is_usable = True - + def __init__(self): MakoLoader.__init__(self, FilesystemLoader()) - + + class MakoAppDirectoriesLoader(MakoLoader): is_usable = True - + def __init__(self): MakoLoader.__init__(self, AppDirectoriesLoader()) diff --git a/common/djangoapps/mitxmako/template.py b/common/djangoapps/mitxmako/template.py index 947dc8c1a4..6ef8058c7c 100644 --- a/common/djangoapps/mitxmako/template.py +++ b/common/djangoapps/mitxmako/template.py @@ -20,13 +20,15 @@ from mitxmako import middleware django_variables = ['lookup', 'output_encoding', 'encoding_errors'] # TODO: We should make this a Django Template subclass that simply has the MakoTemplate inside of it? (Intead of inheriting from MakoTemplate) + + class Template(MakoTemplate): """ This bridges the gap between a Mako template and a djano template. It can be rendered like it is a django template because the arguments are transformed in a way that MakoTemplate can understand. """ - + def __init__(self, *args, **kwargs): """Overrides base __init__ to provide django variable overrides""" if not kwargs.get('no_django', False): @@ -34,8 +36,8 @@ class Template(MakoTemplate): overrides['lookup'] = overrides['lookup']['main'] kwargs.update(overrides) super(Template, self).__init__(*args, **kwargs) - - + + def render(self, context_instance): """ This takes a render call with a context (from Django) and translates @@ -43,7 +45,7 @@ class Template(MakoTemplate): """ # collapse context_instance to a single dictionary for mako context_dictionary = {} - + # In various testing contexts, there might not be a current request context. if middleware.requestcontext is not None: for d in middleware.requestcontext: @@ -53,5 +55,5 @@ class Template(MakoTemplate): context_dictionary['settings'] = settings context_dictionary['MITX_ROOT_URL'] = settings.MITX_ROOT_URL context_dictionary['django_context'] = context_instance - + return super(Template, self).render_unicode(**context_dictionary) diff --git a/common/djangoapps/mitxmako/templatetag_helpers.py b/common/djangoapps/mitxmako/templatetag_helpers.py index e254625d3d..cd871a0fc5 100644 --- a/common/djangoapps/mitxmako/templatetag_helpers.py +++ b/common/djangoapps/mitxmako/templatetag_helpers.py @@ -2,14 +2,15 @@ from django.template import loader from django.template.base import Template, Context from django.template.loader import get_template, select_template + def django_template_include(file_name, mako_context): """ This can be used within a mako template to include a django template in the way that a django-style {% include %} does. Pass it context which can be the mako context ('context') or a dictionary. """ - - dictionary = dict( mako_context ) + + dictionary = dict(mako_context) return loader.render_to_string(file_name, dictionary=dictionary) @@ -18,7 +19,7 @@ def render_inclusion(func, file_name, takes_context, django_context, *args, **kw This allows a mako template to call a template tag function (written for django templates) that is an "inclusion tag". These functions are decorated with @register.inclusion_tag. - + -func: This is the function that is registered as an inclusion tag. You must import it directly using a python import statement. -file_name: This is the filename of the template, passed into the @@ -29,10 +30,10 @@ def render_inclusion(func, file_name, takes_context, django_context, *args, **kw a copy of the django context is available as 'django_context'. -*args and **kwargs are the arguments to func. """ - + if takes_context: args = [django_context] + list(args) - + _dict = func(*args, **kwargs) if isinstance(file_name, Template): t = file_name @@ -40,14 +41,12 @@ def render_inclusion(func, file_name, takes_context, django_context, *args, **kw t = select_template(file_name) else: t = get_template(file_name) - + nodelist = t.nodelist - + new_context = Context(_dict) csrf_token = django_context.get('csrf_token', None) if csrf_token is not None: new_context['csrf_token'] = csrf_token - - return nodelist.render(new_context) - + return nodelist.render(new_context) diff --git a/common/djangoapps/static_replace/__init__.py b/common/djangoapps/static_replace/__init__.py index cfef798bdf..6bd8125580 100644 --- a/common/djangoapps/static_replace/__init__.py +++ b/common/djangoapps/static_replace/__init__.py @@ -21,6 +21,7 @@ def _url_replace_regex(prefix): (?P=quote) # the first matching closing quote """.format(prefix=prefix) + def try_staticfiles_lookup(path): """ Try to lookup a path in staticfiles_storage. If it fails, return diff --git a/common/djangoapps/static_replace/test/test_static_replace.py b/common/djangoapps/static_replace/test/test_static_replace.py index e08c66c59f..50c0fbd246 100644 --- a/common/djangoapps/static_replace/test/test_static_replace.py +++ b/common/djangoapps/static_replace/test/test_static_replace.py @@ -53,6 +53,7 @@ def test_mongo_filestore(mock_modulestore, mock_static_content): mock_static_content.convert_legacy_static_url.assert_called_once_with('file.png', NAMESPACE) + @patch('static_replace.settings') @patch('static_replace.modulestore') @patch('static_replace.staticfiles_storage') diff --git a/common/djangoapps/status/__init__.py b/common/djangoapps/status/__init__.py index 8b13789179..e69de29bb2 100644 --- a/common/djangoapps/status/__init__.py +++ b/common/djangoapps/status/__init__.py @@ -1 +0,0 @@ - diff --git a/common/djangoapps/status/status.py b/common/djangoapps/status/status.py index c06a70f5a1..deacd9c631 100644 --- a/common/djangoapps/status/status.py +++ b/common/djangoapps/status/status.py @@ -10,6 +10,7 @@ import sys log = logging.getLogger(__name__) + def get_site_status_msg(course_id): """ Look for a file settings.STATUS_MESSAGE_PATH. If found, read it, diff --git a/common/djangoapps/student/management/commands/6002exportusers.py b/common/djangoapps/student/management/commands/6002exportusers.py index fcf565fb83..31d8092d3f 100644 --- a/common/djangoapps/student/management/commands/6002exportusers.py +++ b/common/djangoapps/student/management/commands/6002exportusers.py @@ -57,7 +57,7 @@ from student.userprofile. ''' d[key] = item return d - extracted = [{'up':extract_dict(up_keys, t[0]), 'u':extract_dict(user_keys, t[1])} for t in user_tuples] + extracted = [{'up': extract_dict(up_keys, t[0]), 'u':extract_dict(user_keys, t[1])} for t in user_tuples] fp = open('transfer_users.txt', 'w') json.dump(extracted, fp) fp.close() diff --git a/common/djangoapps/student/management/commands/add_to_group.py b/common/djangoapps/student/management/commands/add_to_group.py index 209d25da85..bdbb4027f7 100644 --- a/common/djangoapps/student/management/commands/add_to_group.py +++ b/common/djangoapps/student/management/commands/add_to_group.py @@ -3,6 +3,7 @@ from optparse import make_option from django.core.management.base import BaseCommand, CommandError from django.contrib.auth.models import User, Group + class Command(BaseCommand): option_list = BaseCommand.option_list + ( make_option('--list', diff --git a/common/djangoapps/student/management/commands/create_random_users.py b/common/djangoapps/student/management/commands/create_random_users.py index c6cf452a43..70374d02f2 100644 --- a/common/djangoapps/student/management/commands/create_random_users.py +++ b/common/djangoapps/student/management/commands/create_random_users.py @@ -8,6 +8,7 @@ from student.models import UserProfile, CourseEnrollment from student.views import _do_create_account, get_random_post_override + def create(n, course_id): """Create n users, enrolling them in course_id if it's not None""" for i in range(n): @@ -15,6 +16,7 @@ def create(n, course_id): if course_id is not None: CourseEnrollment.objects.create(user=user, course_id=course_id) + class Command(BaseCommand): help = """Create N new users, with random parameters. diff --git a/common/djangoapps/student/management/commands/pearson_dump.py b/common/djangoapps/student/management/commands/pearson_dump.py index 228508efb1..2aade8cf5f 100644 --- a/common/djangoapps/student/management/commands/pearson_dump.py +++ b/common/djangoapps/student/management/commands/pearson_dump.py @@ -36,7 +36,7 @@ class Command(BaseCommand): outputfile = datetime.utcnow().strftime("pearson-dump-%Y%m%d-%H%M%S.json") else: outputfile = args[0] - + # construct the query object to dump: registrations = TestCenterRegistration.objects.all() if 'course_id' in options and options['course_id']: @@ -44,24 +44,24 @@ class Command(BaseCommand): if 'exam_series_code' in options and options['exam_series_code']: registrations = registrations.filter(exam_series_code=options['exam_series_code']) - # collect output: + # collect output: output = [] for registration in registrations: if 'accommodation_pending' in options and options['accommodation_pending'] and not registration.accommodation_is_pending: continue - record = {'username' : registration.testcenter_user.user.username, - 'email' : registration.testcenter_user.email, - 'first_name' : registration.testcenter_user.first_name, - 'last_name' : registration.testcenter_user.last_name, - 'client_candidate_id' : registration.client_candidate_id, - 'client_authorization_id' : registration.client_authorization_id, - 'course_id' : registration.course_id, - 'exam_series_code' : registration.exam_series_code, - 'accommodation_request' : registration.accommodation_request, - 'accommodation_code' : registration.accommodation_code, - 'registration_status' : registration.registration_status(), - 'demographics_status' : registration.demographics_status(), - 'accommodation_status' : registration.accommodation_status(), + record = {'username': registration.testcenter_user.user.username, + 'email': registration.testcenter_user.email, + 'first_name': registration.testcenter_user.first_name, + 'last_name': registration.testcenter_user.last_name, + 'client_candidate_id': registration.client_candidate_id, + 'client_authorization_id': registration.client_authorization_id, + 'course_id': registration.course_id, + 'exam_series_code': registration.exam_series_code, + 'accommodation_request': registration.accommodation_request, + 'accommodation_code': registration.accommodation_code, + 'registration_status': registration.registration_status(), + 'demographics_status': registration.demographics_status(), + 'accommodation_status': registration.accommodation_status(), } if len(registration.upload_error_message) > 0: record['registration_error'] = registration.upload_error_message @@ -71,8 +71,7 @@ class Command(BaseCommand): record['needs_uploading'] = True output.append(record) - + # dump output: with open(outputfile, 'w') as outfile: dump(output, outfile, indent=2) - diff --git a/common/djangoapps/student/management/commands/pearson_export_cdd.py b/common/djangoapps/student/management/commands/pearson_export_cdd.py index 463eec6b70..bad98b9d25 100644 --- a/common/djangoapps/student/management/commands/pearson_export_cdd.py +++ b/common/djangoapps/student/management/commands/pearson_export_cdd.py @@ -39,7 +39,7 @@ class Command(BaseCommand): ("LastUpdate", "user_updated_at"), # in UTC, so same as what we store ]) - # define defaults, even thought 'store_true' shouldn't need them. + # define defaults, even thought 'store_true' shouldn't need them. # (call_command will set None as default value for all options that don't have one, # so one cannot rely on presence/absence of flags in that world.) option_list = BaseCommand.option_list + ( @@ -56,7 +56,7 @@ class Command(BaseCommand): ) def handle(self, **options): - # update time should use UTC in order to be comparable to the user_updated_at + # update time should use UTC in order to be comparable to the user_updated_at # field uploaded_at = datetime.utcnow() @@ -100,7 +100,7 @@ class Command(BaseCommand): extrasaction='ignore') writer.writeheader() for tcu in TestCenterUser.objects.order_by('id'): - if tcu.needs_uploading: # or dump_all + if tcu.needs_uploading: # or dump_all record = dict((csv_field, ensure_encoding(getattr(tcu, model_field))) for csv_field, model_field in Command.CSV_TO_MODEL_FIELDS.items()) diff --git a/common/djangoapps/student/management/commands/pearson_import_conf_zip.py b/common/djangoapps/student/management/commands/pearson_import_conf_zip.py index d94c3ba863..d0b2938df0 100644 --- a/common/djangoapps/student/management/commands/pearson_import_conf_zip.py +++ b/common/djangoapps/student/management/commands/pearson_import_conf_zip.py @@ -116,4 +116,3 @@ class Command(BaseCommand): tcuser.save() except TestCenterUser.DoesNotExist: Command.datadog_error(" Failed to find record for client_candidate_id {}".format(client_candidate_id), vcdcfile.name) - diff --git a/common/djangoapps/student/management/commands/pearson_make_tc_registration.py b/common/djangoapps/student/management/commands/pearson_make_tc_registration.py index b59241240d..b10cf143a0 100644 --- a/common/djangoapps/student/management/commands/pearson_make_tc_registration.py +++ b/common/djangoapps/student/management/commands/pearson_make_tc_registration.py @@ -9,6 +9,7 @@ from student.views import course_from_id from xmodule.course_module import CourseDescriptor from xmodule.modulestore.exceptions import ItemNotFoundError + class Command(BaseCommand): option_list = BaseCommand.option_list + ( # registration info: @@ -16,23 +17,23 @@ class Command(BaseCommand): '--accommodation_request', action='store', dest='accommodation_request', - ), + ), make_option( '--accommodation_code', action='store', dest='accommodation_code', - ), + ), make_option( '--client_authorization_id', action='store', dest='client_authorization_id', - ), - # exam info: + ), + # exam info: make_option( '--exam_series_code', action='store', dest='exam_series_code', - ), + ), make_option( '--eligibility_appointment_date_first', action='store', @@ -51,32 +52,32 @@ class Command(BaseCommand): action='store', dest='authorization_id', help='ID we receive from Pearson for a particular authorization' - ), + ), make_option( '--upload_status', action='store', dest='upload_status', help='status value assigned by Pearson' - ), + ), make_option( '--upload_error_message', action='store', dest='upload_error_message', help='error message provided by Pearson on a failure.' - ), + ), # control values: make_option( '--ignore_registration_dates', action='store_true', dest='ignore_registration_dates', help='find exam info for course based on exam_series_code, even if the exam is not active.' - ), + ), make_option( '--create_dummy_exam', action='store_true', dest='create_dummy_exam', help='create dummy exam info for course, even if course exists' - ), + ), ) args = "" help = "Create or modify a TestCenterRegistration entry for a given Student" @@ -103,7 +104,7 @@ class Command(BaseCommand): testcenter_user = TestCenterUser.objects.get(user=student) except TestCenterUser.DoesNotExist: raise CommandError("User \"{}\" does not have an existing demographics record".format(username)) - + # get an "exam" object. Check to see if a course_id was specified, and use information from that: exam = None create_dummy_exam = 'create_dummy_exam' in our_options and our_options['create_dummy_exam'] @@ -115,14 +116,14 @@ class Command(BaseCommand): exam = examlist[0] if len(examlist) > 0 else None else: exam = course.current_test_center_exam - except ItemNotFoundError: + except ItemNotFoundError: pass else: - # otherwise use explicit values (so we don't have to define a course): + # otherwise use explicit values (so we don't have to define a course): exam_name = "Dummy Placeholder Name" - exam_info = { 'Exam_Series_Code': our_options['exam_series_code'], - 'First_Eligible_Appointment_Date' : our_options['eligibility_appointment_date_first'], - 'Last_Eligible_Appointment_Date' : our_options['eligibility_appointment_date_last'], + exam_info = {'Exam_Series_Code': our_options['exam_series_code'], + 'First_Eligible_Appointment_Date': our_options['eligibility_appointment_date_first'], + 'Last_Eligible_Appointment_Date': our_options['eligibility_appointment_date_last'], } exam = CourseDescriptor.TestCenterExam(course_id, exam_name, exam_info) # update option values for date_first and date_last to use YYYY-MM-DD format @@ -134,15 +135,15 @@ class Command(BaseCommand): raise CommandError("Exam for course_id {} does not exist".format(course_id)) exam_code = exam.exam_series_code - - UPDATE_FIELDS = ( 'accommodation_request', + + UPDATE_FIELDS = ('accommodation_request', 'accommodation_code', 'client_authorization_id', 'exam_series_code', 'eligibility_appointment_date_first', 'eligibility_appointment_date_last', ) - + # create and save the registration: needs_updating = False registrations = get_testcenter_registration(student, course_id, exam_code) @@ -152,29 +153,29 @@ class Command(BaseCommand): if fieldname in our_options and registration.__getattribute__(fieldname) != our_options[fieldname]: needs_updating = True; else: - accommodation_request = our_options.get('accommodation_request','') + accommodation_request = our_options.get('accommodation_request', '') registration = TestCenterRegistration.create(testcenter_user, exam, accommodation_request) needs_updating = True - + if needs_updating: # first update the record with the new values, if any: for fieldname in UPDATE_FIELDS: - if fieldname in our_options and fieldname not in TestCenterRegistrationForm.Meta.fields: + if fieldname in our_options and fieldname not in TestCenterRegistrationForm.Meta.fields: registration.__setattr__(fieldname, our_options[fieldname]) - - # the registration form normally populates the data dict with + + # the registration form normally populates the data dict with # the accommodation request (if any). But here we want to # specify only those values that might change, so update the dict with existing # values. form_options = dict(our_options) for propname in TestCenterRegistrationForm.Meta.fields: - if propname not in form_options: + if propname not in form_options: form_options[propname] = registration.__getattribute__(propname) form = TestCenterRegistrationForm(instance=registration, data=form_options) if form.is_valid(): form.update_and_save() - print "Updated registration information for user's registration: username \"{}\" course \"{}\", examcode \"{}\"".format(student.username, course_id, exam_code) + print "Updated registration information for user's registration: username \"{}\" course \"{}\", examcode \"{}\"".format(student.username, course_id, exam_code) else: if (len(form.errors) > 0): print "Field Form errors encountered:" @@ -185,24 +186,22 @@ class Command(BaseCommand): print "Non-field Form errors encountered:" for nonfielderror in form.non_field_errors: print "Non-field Form Error: %s" % nonfielderror - + else: print "No changes necessary to make to existing user's registration." - + # override internal values: change_internal = False if 'exam_series_code' in our_options: exam_code = our_options['exam_series_code'] registration = get_testcenter_registration(student, course_id, exam_code)[0] - for internal_field in [ 'upload_error_message', 'upload_status', 'authorization_id']: + for internal_field in ['upload_error_message', 'upload_status', 'authorization_id']: if internal_field in our_options: registration.__setattr__(internal_field, our_options[internal_field]) change_internal = True - + if change_internal: print "Updated confirmation information in existing user's registration." registration.save() else: print "No changes necessary to make to confirmation information in existing user's registration." - - diff --git a/common/djangoapps/student/management/commands/pearson_make_tc_user.py b/common/djangoapps/student/management/commands/pearson_make_tc_user.py index 87e0b4dadd..10ef0bd067 100644 --- a/common/djangoapps/student/management/commands/pearson_make_tc_user.py +++ b/common/djangoapps/student/management/commands/pearson_make_tc_user.py @@ -5,60 +5,61 @@ from django.core.management.base import BaseCommand, CommandError from student.models import TestCenterUser, TestCenterUserForm + class Command(BaseCommand): option_list = BaseCommand.option_list + ( - # demographics: + # demographics: make_option( '--first_name', action='store', dest='first_name', - ), + ), make_option( '--middle_name', action='store', dest='middle_name', - ), + ), make_option( '--last_name', action='store', dest='last_name', - ), + ), make_option( '--suffix', action='store', dest='suffix', - ), + ), make_option( '--salutation', action='store', dest='salutation', - ), + ), make_option( '--address_1', action='store', dest='address_1', - ), + ), make_option( '--address_2', action='store', dest='address_2', - ), + ), make_option( '--address_3', action='store', dest='address_3', - ), + ), make_option( '--city', action='store', dest='city', - ), + ), make_option( '--state', action='store', dest='state', help='Two letter code (e.g. MA)' - ), + ), make_option( '--postal_code', action='store', @@ -75,12 +76,12 @@ class Command(BaseCommand): action='store', dest='phone', help='Pretty free-form (parens, spaces, dashes), but no country code' - ), + ), make_option( '--extension', action='store', dest='extension', - ), + ), make_option( '--phone_country_code', action='store', @@ -92,7 +93,7 @@ class Command(BaseCommand): action='store', dest='fax', help='Pretty free-form (parens, spaces, dashes), but no country code' - ), + ), make_option( '--fax_country_code', action='store', @@ -103,26 +104,26 @@ class Command(BaseCommand): '--company_name', action='store', dest='company_name', - ), + ), # internal values: make_option( '--client_candidate_id', action='store', dest='client_candidate_id', help='ID we assign a user to identify them to Pearson' - ), + ), make_option( '--upload_status', action='store', dest='upload_status', help='status value assigned by Pearson' - ), + ), make_option( '--upload_error_message', action='store', dest='upload_error_message', help='error message provided by Pearson on a failure.' - ), + ), ) args = "" help = "Create or modify a TestCenterUser entry for a given Student" @@ -142,20 +143,20 @@ class Command(BaseCommand): student = User.objects.get(username=username) try: testcenter_user = TestCenterUser.objects.get(user=student) - needs_updating = testcenter_user.needs_update(our_options) + needs_updating = testcenter_user.needs_update(our_options) except TestCenterUser.DoesNotExist: # do additional initialization here: testcenter_user = TestCenterUser.create(student) needs_updating = True - + if needs_updating: - # the registration form normally populates the data dict with + # the registration form normally populates the data dict with # all values from the testcenter_user. But here we only want to # specify those values that change, so update the dict with existing # values. form_options = dict(our_options) for propname in TestCenterUser.user_provided_fields(): - if propname not in form_options: + if propname not in form_options: form_options[propname] = testcenter_user.__getattribute__(propname) form = TestCenterUserForm(instance=testcenter_user, data=form_options) if form.is_valid(): @@ -170,21 +171,20 @@ class Command(BaseCommand): errorlist.append("Non-field Form errors encountered:") for nonfielderror in form.non_field_errors: errorlist.append("Non-field Form Error: {}".format(nonfielderror)) - raise CommandError("\n".join(errorlist)) + raise CommandError("\n".join(errorlist)) else: print "No changes necessary to make to existing user's demographics." - + # override internal values: change_internal = False testcenter_user = TestCenterUser.objects.get(user=student) - for internal_field in [ 'upload_error_message', 'upload_status', 'client_candidate_id']: + for internal_field in ['upload_error_message', 'upload_status', 'client_candidate_id']: if internal_field in our_options: testcenter_user.__setattr__(internal_field, our_options[internal_field]) change_internal = True - + if change_internal: testcenter_user.save() print "Updated confirmation information in existing user's demographics." else: print "No changes necessary to make to confirmation information in existing user's demographics." - diff --git a/common/djangoapps/student/management/commands/pearson_transfer.py b/common/djangoapps/student/management/commands/pearson_transfer.py index 6811e1833d..5eded6484a 100644 --- a/common/djangoapps/student/management/commands/pearson_transfer.py +++ b/common/djangoapps/student/management/commands/pearson_transfer.py @@ -46,10 +46,10 @@ class Command(BaseCommand): if not hasattr(settings, value): raise CommandError('No entry in the AWS settings' '(env/auth.json) for {0}'.format(value)) - + # check additional required settings for import and export: if options['mode'] in ('export', 'both'): - for value in ['LOCAL_EXPORT','SFTP_EXPORT']: + for value in ['LOCAL_EXPORT', 'SFTP_EXPORT']: if value not in settings.PEARSON: raise CommandError('No entry in the PEARSON settings' '(env/auth.json) for {0}'.format(value)) @@ -57,9 +57,9 @@ class Command(BaseCommand): source_dir = settings.PEARSON['LOCAL_EXPORT'] if not os.path.isdir(source_dir): os.makedirs(source_dir) - + if options['mode'] in ('import', 'both'): - for value in ['LOCAL_IMPORT','SFTP_IMPORT']: + for value in ['LOCAL_IMPORT', 'SFTP_IMPORT']: if value not in settings.PEARSON: raise CommandError('No entry in the PEARSON settings' '(env/auth.json) for {0}'.format(value)) @@ -76,7 +76,7 @@ class Command(BaseCommand): t.connect(username=settings.PEARSON['SFTP_USERNAME'], password=settings.PEARSON['SFTP_PASSWORD']) sftp = paramiko.SFTPClient.from_transport(t) - + if mode == 'export': try: sftp.chdir(files_to) @@ -92,7 +92,7 @@ class Command(BaseCommand): except IOError: raise CommandError('SFTP source path does not exist: {}'.format(files_from)) for filename in sftp.listdir('.'): - # skip subdirectories + # skip subdirectories if not S_ISDIR(sftp.stat(filename).st_mode): sftp.get(filename, files_to + '/' + filename) # delete files from sftp server once they are successfully pulled off: @@ -112,7 +112,7 @@ class Command(BaseCommand): try: for filename in os.listdir(files_from): source_file = os.path.join(files_from, filename) - # use mode as name of directory into which to write files + # use mode as name of directory into which to write files dest_file = os.path.join(mode, filename) upload_file_to_s3(bucket, source_file, dest_file) if deleteAfterCopy: @@ -135,17 +135,17 @@ class Command(BaseCommand): k.set_contents_from_filename(source_file) def export_pearson(): - options = { 'dest-from-settings' : True } + options = {'dest-from-settings': True} call_command('pearson_export_cdd', **options) call_command('pearson_export_ead', **options) mode = 'export' - sftp(settings.PEARSON['LOCAL_EXPORT'], settings.PEARSON['SFTP_EXPORT'], mode, deleteAfterCopy = False) + sftp(settings.PEARSON['LOCAL_EXPORT'], settings.PEARSON['SFTP_EXPORT'], mode, deleteAfterCopy=False) s3(settings.PEARSON['LOCAL_EXPORT'], settings.PEARSON['S3_BUCKET'], mode, deleteAfterCopy=True) def import_pearson(): mode = 'import' try: - sftp(settings.PEARSON['SFTP_IMPORT'], settings.PEARSON['LOCAL_IMPORT'], mode, deleteAfterCopy = True) + sftp(settings.PEARSON['SFTP_IMPORT'], settings.PEARSON['LOCAL_IMPORT'], mode, deleteAfterCopy=True) s3(settings.PEARSON['LOCAL_IMPORT'], settings.PEARSON['S3_BUCKET'], mode, deleteAfterCopy=False) except Exception as e: dog_http_api.event('Pearson Import failure', str(e)) diff --git a/common/djangoapps/student/management/commands/tests/test_pearson.py b/common/djangoapps/student/management/commands/tests/test_pearson.py index 2f4878a09d..12969405de 100644 --- a/common/djangoapps/student/management/commands/tests/test_pearson.py +++ b/common/djangoapps/student/management/commands/tests/test_pearson.py @@ -17,30 +17,31 @@ from student.models import User, TestCenterRegistration, TestCenterUser, get_tes log = logging.getLogger(__name__) + def create_tc_user(username): user = User.objects.create_user(username, '{}@edx.org'.format(username), 'fakepass') options = { - 'first_name' : 'TestFirst', - 'last_name' : 'TestLast', - 'address_1' : 'Test Address', - 'city' : 'TestCity', - 'state' : 'Alberta', - 'postal_code' : 'A0B 1C2', - 'country' : 'CAN', - 'phone' : '252-1866', - 'phone_country_code' : '1', + 'first_name': 'TestFirst', + 'last_name': 'TestLast', + 'address_1': 'Test Address', + 'city': 'TestCity', + 'state': 'Alberta', + 'postal_code': 'A0B 1C2', + 'country': 'CAN', + 'phone': '252-1866', + 'phone_country_code': '1', } call_command('pearson_make_tc_user', username, **options) return TestCenterUser.objects.get(user=user) - - -def create_tc_registration(username, course_id = 'org1/course1/term1', exam_code = 'exam1', accommodation_code = None): - - options = { 'exam_series_code' : exam_code, - 'eligibility_appointment_date_first' : '2013-01-01T00:00', - 'eligibility_appointment_date_last' : '2013-12-31T23:59', - 'accommodation_code' : accommodation_code, - 'create_dummy_exam' : True, + + +def create_tc_registration(username, course_id='org1/course1/term1', exam_code='exam1', accommodation_code=None): + + options = {'exam_series_code': exam_code, + 'eligibility_appointment_date_first': '2013-01-01T00:00', + 'eligibility_appointment_date_last': '2013-12-31T23:59', + 'accommodation_code': accommodation_code, + 'create_dummy_exam': True, } call_command('pearson_make_tc_registration', username, course_id, **options) @@ -48,21 +49,23 @@ def create_tc_registration(username, course_id = 'org1/course1/term1', exam_code registrations = get_testcenter_registration(user, course_id, exam_code) return registrations[0] + def create_multiple_registrations(prefix='test'): username1 = '{}_multiple1'.format(prefix) create_tc_user(username1) create_tc_registration(username1) - create_tc_registration(username1, course_id = 'org1/course2/term1') - create_tc_registration(username1, exam_code = 'exam2') + create_tc_registration(username1, course_id='org1/course2/term1') + create_tc_registration(username1, exam_code='exam2') username2 = '{}_multiple2'.format(prefix) create_tc_user(username2) create_tc_registration(username2) username3 = '{}_multiple3'.format(prefix) create_tc_user(username3) - create_tc_registration(username3, course_id = 'org1/course2/term1') + create_tc_registration(username3, course_id='org1/course2/term1') username4 = '{}_multiple4'.format(prefix) create_tc_user(username4) - create_tc_registration(username4, exam_code = 'exam2') + create_tc_registration(username4, exam_code='exam2') + def get_command_error_text(*args, **options): stderr_string = None @@ -75,21 +78,22 @@ def get_command_error_text(*args, **options): # But these are actually translated into nice messages, # and sys.exit(1) is then called. For testing, we # want to catch what sys.exit throws, and get the - # relevant text either from stdout or stderr. + # relevant text either from stdout or stderr. if (why1.message > 0): stderr_string = sys.stderr.getvalue() else: raise why1 except Exception, why: raise why - + finally: sys.stderr = old_stderr - + if stderr_string is None: - raise Exception("Expected call to {} to fail, but it succeeded!".format(args[0])) + raise Exception("Expected call to {} to fail, but it succeeded!".format(args[0])) return stderr_string - + + def get_error_string_for_management_call(*args, **options): stdout_string = None old_stdout = sys.stdout @@ -103,7 +107,7 @@ def get_error_string_for_management_call(*args, **options): # But these are actually translated into nice messages, # and sys.exit(1) is then called. For testing, we # want to catch what sys.exit throws, and get the - # relevant text either from stdout or stderr. + # relevant text either from stdout or stderr. if (why1.message == 1): stdout_string = sys.stdout.getvalue() stderr_string = sys.stderr.getvalue() @@ -111,15 +115,15 @@ def get_error_string_for_management_call(*args, **options): raise why1 except Exception, why: raise why - + finally: sys.stdout = old_stdout sys.stderr = old_stderr - + if stdout_string is None: - raise Exception("Expected call to {} to fail, but it succeeded!".format(args[0])) + raise Exception("Expected call to {} to fail, but it succeeded!".format(args[0])) return stdout_string, stderr_string - + def get_file_info(dirpath): filelist = os.listdir(dirpath) @@ -132,43 +136,45 @@ def get_file_info(dirpath): numlines = len(filecontents) return filepath, numlines else: - raise Exception("Expected to find a single file in {}, but found {}".format(dirpath,filelist)) - + raise Exception("Expected to find a single file in {}, but found {}".format(dirpath, filelist)) + + class PearsonTestCase(TestCase): ''' Base class for tests running Pearson-related commands ''' import_dir = mkdtemp(prefix="import") export_dir = mkdtemp(prefix="export") - + def assertErrorContains(self, error_message, expected): self.assertTrue(error_message.find(expected) >= 0, 'error message "{}" did not contain "{}"'.format(error_message, expected)) - + def tearDown(self): def delete_temp_dir(dirname): if os.path.exists(dirname): for filename in os.listdir(dirname): os.remove(os.path.join(dirname, filename)) os.rmdir(dirname) - + # clean up after any test data was dumped to temp directory delete_temp_dir(self.import_dir) delete_temp_dir(self.export_dir) - + # and clean up the database: # TestCenterUser.objects.all().delete() # TestCenterRegistration.objects.all().delete() + class PearsonCommandTestCase(PearsonTestCase): def test_missing_demographic_fields(self): - # We won't bother to test all details of form validation here. + # We won't bother to test all details of form validation here. # It is enough to show that it works here, but deal with test cases for the form # validation in the student tests, not these management tests. username = 'baduser' User.objects.create_user(username, '{}@edx.org'.format(username), 'fakepass') options = {} - error_string = get_command_error_text('pearson_make_tc_user', username, **options) + error_string = get_command_error_text('pearson_make_tc_user', username, **options) self.assertTrue(error_string.find('Field Form errors encountered:') >= 0) self.assertTrue(error_string.find('Field Form Error: city') >= 0) self.assertTrue(error_string.find('Field Form Error: first_name') >= 0) @@ -178,11 +184,11 @@ class PearsonCommandTestCase(PearsonTestCase): self.assertTrue(error_string.find('Field Form Error: phone') >= 0) self.assertTrue(error_string.find('Field Form Error: address_1') >= 0) self.assertErrorContains(error_string, 'Field Form Error: address_1') - + def test_create_good_testcenter_user(self): testcenter_user = create_tc_user("test_good_user") self.assertIsNotNone(testcenter_user) - + def test_create_good_testcenter_registration(self): username = 'test_good_registration' create_tc_user(username) @@ -192,21 +198,21 @@ class PearsonCommandTestCase(PearsonTestCase): def test_cdd_missing_option(self): error_string = get_command_error_text('pearson_export_cdd', **{}) self.assertErrorContains(error_string, 'Error: --destination or --dest-from-settings must be used') - + def test_ead_missing_option(self): error_string = get_command_error_text('pearson_export_ead', **{}) self.assertErrorContains(error_string, 'Error: --destination or --dest-from-settings must be used') def test_export_single_cdd(self): # before we generate any tc_users, we expect there to be nothing to output: - options = { 'dest-from-settings' : True } - with self.settings(PEARSON={ 'LOCAL_EXPORT' : self.export_dir }): + options = {'dest-from-settings': True} + with self.settings(PEARSON={'LOCAL_EXPORT': self.export_dir}): call_command('pearson_export_cdd', **options) (filepath, numlines) = get_file_info(self.export_dir) self.assertEquals(numlines, 1, "Expect cdd file to have no non-header lines") os.remove(filepath) - # generating a tc_user should result in a line in the output + # generating a tc_user should result in a line in the output username = 'test_single_cdd' create_tc_user(username) call_command('pearson_export_cdd', **options) @@ -221,23 +227,23 @@ class PearsonCommandTestCase(PearsonTestCase): os.remove(filepath) # if we modify the record, then it should be output again: - user_options = { 'first_name' : 'NewTestFirst', } + user_options = {'first_name': 'NewTestFirst', } call_command('pearson_make_tc_user', username, **user_options) call_command('pearson_export_cdd', **options) (filepath, numlines) = get_file_info(self.export_dir) self.assertEquals(numlines, 2, "Expect cdd file to have one non-header line") os.remove(filepath) - + def test_export_single_ead(self): # before we generate any registrations, we expect there to be nothing to output: - options = { 'dest-from-settings' : True } - with self.settings(PEARSON={ 'LOCAL_EXPORT' : self.export_dir }): + options = {'dest-from-settings': True} + with self.settings(PEARSON={'LOCAL_EXPORT': self.export_dir}): call_command('pearson_export_ead', **options) (filepath, numlines) = get_file_info(self.export_dir) self.assertEquals(numlines, 1, "Expect ead file to have no non-header lines") os.remove(filepath) - # generating a registration should result in a line in the output + # generating a registration should result in a line in the output username = 'test_single_ead' create_tc_user(username) create_tc_registration(username) @@ -251,7 +257,7 @@ class PearsonCommandTestCase(PearsonTestCase): (filepath, numlines) = get_file_info(self.export_dir) self.assertEquals(numlines, 1, "Expect ead file to have no non-header lines") os.remove(filepath) - + # if we modify the record, then it should be output again: create_tc_registration(username, accommodation_code='EQPMNT') call_command('pearson_export_ead', **options) @@ -261,8 +267,8 @@ class PearsonCommandTestCase(PearsonTestCase): def test_export_multiple(self): create_multiple_registrations("export") - with self.settings(PEARSON={ 'LOCAL_EXPORT' : self.export_dir }): - options = { 'dest-from-settings' : True } + with self.settings(PEARSON={'LOCAL_EXPORT': self.export_dir}): + options = {'dest-from-settings': True} call_command('pearson_export_cdd', **options) (filepath, numlines) = get_file_info(self.export_dir) self.assertEquals(numlines, 5, "Expect cdd file to have four non-header lines: total was {}".format(numlines)) @@ -294,6 +300,7 @@ S3_BUCKET = 'edx-pearson-archive' AWS_ACCESS_KEY_ID = 'put yours here' AWS_SECRET_ACCESS_KEY = 'put yours here' + class PearsonTransferTestCase(PearsonTestCase): ''' Class for tests running Pearson transfers @@ -302,14 +309,14 @@ class PearsonTransferTestCase(PearsonTestCase): def test_transfer_config(self): with self.settings(DATADOG_API='FAKE_KEY'): # TODO: why is this failing with the wrong error message?! - stderrmsg = get_command_error_text('pearson_transfer', **{'mode' : 'garbage'}) + stderrmsg = get_command_error_text('pearson_transfer', **{'mode': 'garbage'}) self.assertErrorContains(stderrmsg, 'Error: No PEARSON entries') with self.settings(DATADOG_API='FAKE_KEY'): stderrmsg = get_command_error_text('pearson_transfer') self.assertErrorContains(stderrmsg, 'Error: No PEARSON entries') with self.settings(DATADOG_API='FAKE_KEY', - PEARSON={'LOCAL_EXPORT' : self.export_dir, - 'LOCAL_IMPORT' : self.import_dir }): + PEARSON={'LOCAL_EXPORT': self.export_dir, + 'LOCAL_IMPORT': self.import_dir}): stderrmsg = get_command_error_text('pearson_transfer') self.assertErrorContains(stderrmsg, 'Error: No entry in the PEARSON settings') @@ -317,16 +324,16 @@ class PearsonTransferTestCase(PearsonTestCase): raise SkipTest() create_multiple_registrations('export_missing_dest') with self.settings(DATADOG_API='FAKE_KEY', - PEARSON={'LOCAL_EXPORT' : self.export_dir, - 'SFTP_EXPORT' : 'this/does/not/exist', - 'SFTP_HOSTNAME' : SFTP_HOSTNAME, - 'SFTP_USERNAME' : SFTP_USERNAME, - 'SFTP_PASSWORD' : SFTP_PASSWORD, - 'S3_BUCKET' : S3_BUCKET, + PEARSON={'LOCAL_EXPORT': self.export_dir, + 'SFTP_EXPORT': 'this/does/not/exist', + 'SFTP_HOSTNAME': SFTP_HOSTNAME, + 'SFTP_USERNAME': SFTP_USERNAME, + 'SFTP_PASSWORD': SFTP_PASSWORD, + 'S3_BUCKET': S3_BUCKET, }, - AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID, - AWS_SECRET_ACCESS_KEY = AWS_SECRET_ACCESS_KEY): - options = { 'mode' : 'export'} + AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY=AWS_SECRET_ACCESS_KEY): + options = {'mode': 'export'} stderrmsg = get_command_error_text('pearson_transfer', **options) self.assertErrorContains(stderrmsg, 'Error: SFTP destination path does not exist') @@ -334,16 +341,16 @@ class PearsonTransferTestCase(PearsonTestCase): raise SkipTest() create_multiple_registrations("transfer_export") with self.settings(DATADOG_API='FAKE_KEY', - PEARSON={'LOCAL_EXPORT' : self.export_dir, - 'SFTP_EXPORT' : 'results/topvue', - 'SFTP_HOSTNAME' : SFTP_HOSTNAME, - 'SFTP_USERNAME' : SFTP_USERNAME, - 'SFTP_PASSWORD' : SFTP_PASSWORD, - 'S3_BUCKET' : S3_BUCKET, + PEARSON={'LOCAL_EXPORT': self.export_dir, + 'SFTP_EXPORT': 'results/topvue', + 'SFTP_HOSTNAME': SFTP_HOSTNAME, + 'SFTP_USERNAME': SFTP_USERNAME, + 'SFTP_PASSWORD': SFTP_PASSWORD, + 'S3_BUCKET': S3_BUCKET, }, - AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID, - AWS_SECRET_ACCESS_KEY = AWS_SECRET_ACCESS_KEY): - options = { 'mode' : 'export'} + AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY=AWS_SECRET_ACCESS_KEY): + options = {'mode': 'export'} # call_command('pearson_transfer', **options) # # confirm that the export directory is still empty: # self.assertEqual(len(os.listdir(self.export_dir)), 0, "expected export directory to be empty") @@ -352,16 +359,16 @@ class PearsonTransferTestCase(PearsonTestCase): raise SkipTest() create_multiple_registrations('import_missing_src') with self.settings(DATADOG_API='FAKE_KEY', - PEARSON={'LOCAL_IMPORT' : self.import_dir, - 'SFTP_IMPORT' : 'this/does/not/exist', - 'SFTP_HOSTNAME' : SFTP_HOSTNAME, - 'SFTP_USERNAME' : SFTP_USERNAME, - 'SFTP_PASSWORD' : SFTP_PASSWORD, - 'S3_BUCKET' : S3_BUCKET, + PEARSON={'LOCAL_IMPORT': self.import_dir, + 'SFTP_IMPORT': 'this/does/not/exist', + 'SFTP_HOSTNAME': SFTP_HOSTNAME, + 'SFTP_USERNAME': SFTP_USERNAME, + 'SFTP_PASSWORD': SFTP_PASSWORD, + 'S3_BUCKET': S3_BUCKET, }, - AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID, - AWS_SECRET_ACCESS_KEY = AWS_SECRET_ACCESS_KEY): - options = { 'mode' : 'import'} + AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY=AWS_SECRET_ACCESS_KEY): + options = {'mode': 'import'} stderrmsg = get_command_error_text('pearson_transfer', **options) self.assertErrorContains(stderrmsg, 'Error: SFTP source path does not exist') @@ -369,15 +376,15 @@ class PearsonTransferTestCase(PearsonTestCase): raise SkipTest() create_multiple_registrations('import_missing_src') with self.settings(DATADOG_API='FAKE_KEY', - PEARSON={'LOCAL_IMPORT' : self.import_dir, - 'SFTP_IMPORT' : 'results', - 'SFTP_HOSTNAME' : SFTP_HOSTNAME, - 'SFTP_USERNAME' : SFTP_USERNAME, - 'SFTP_PASSWORD' : SFTP_PASSWORD, - 'S3_BUCKET' : S3_BUCKET, + PEARSON={'LOCAL_IMPORT': self.import_dir, + 'SFTP_IMPORT': 'results', + 'SFTP_HOSTNAME': SFTP_HOSTNAME, + 'SFTP_USERNAME': SFTP_USERNAME, + 'SFTP_PASSWORD': SFTP_PASSWORD, + 'S3_BUCKET': S3_BUCKET, }, - AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID, - AWS_SECRET_ACCESS_KEY = AWS_SECRET_ACCESS_KEY): - options = { 'mode' : 'import'} + AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY=AWS_SECRET_ACCESS_KEY): + options = {'mode': 'import'} call_command('pearson_transfer', **options) self.assertEqual(len(os.listdir(self.import_dir)), 0, "expected import directory to be empty") diff --git a/common/djangoapps/student/migrations/0020_add_test_center_user.py b/common/djangoapps/student/migrations/0020_add_test_center_user.py index e308e2d7e0..6c0bf5c4ee 100644 --- a/common/djangoapps/student/migrations/0020_add_test_center_user.py +++ b/common/djangoapps/student/migrations/0020_add_test_center_user.py @@ -185,4 +185,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['student'] \ No newline at end of file + complete_apps = ['student'] diff --git a/common/djangoapps/student/migrations/0021_remove_askbot.py b/common/djangoapps/student/migrations/0021_remove_askbot.py index 83ad6791f2..8f76e5078c 100644 --- a/common/djangoapps/student/migrations/0021_remove_askbot.py +++ b/common/djangoapps/student/migrations/0021_remove_askbot.py @@ -36,7 +36,7 @@ class Migration(SchemaMigration): for column in ASKBOT_AUTH_USER_COLUMNS: db.delete_column('auth_user', column) except Exception as ex: - print "Couldn't remove askbot because of {0} -- it was probably never here to begin with.".format(ex) + print "Couldn't remove askbot because of {0} -- it was probably never here to begin with.".format(ex) def backwards(self, orm): raise RuntimeError("Cannot reverse this migration: there's no going back to Askbot.") diff --git a/common/djangoapps/student/migrations/0022_auto__add_courseenrollmentallowed__add_unique_courseenrollmentallowed_.py b/common/djangoapps/student/migrations/0022_auto__add_courseenrollmentallowed__add_unique_courseenrollmentallowed_.py index f7e2571685..769ad6737d 100644 --- a/common/djangoapps/student/migrations/0022_auto__add_courseenrollmentallowed__add_unique_courseenrollmentallowed_.py +++ b/common/djangoapps/student/migrations/0022_auto__add_courseenrollmentallowed__add_unique_courseenrollmentallowed_.py @@ -152,4 +152,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['student'] \ No newline at end of file + complete_apps = ['student'] diff --git a/common/djangoapps/student/migrations/0023_add_test_center_registration.py b/common/djangoapps/student/migrations/0023_add_test_center_registration.py index c5af38dd37..4c7de6dcd9 100644 --- a/common/djangoapps/student/migrations/0023_add_test_center_registration.py +++ b/common/djangoapps/student/migrations/0023_add_test_center_registration.py @@ -238,4 +238,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['student'] \ No newline at end of file + complete_apps = ['student'] diff --git a/common/djangoapps/student/migrations/0024_add_allow_certificate.py b/common/djangoapps/student/migrations/0024_add_allow_certificate.py index fb3a97cd4b..56eccf8d70 100644 --- a/common/djangoapps/student/migrations/0024_add_allow_certificate.py +++ b/common/djangoapps/student/migrations/0024_add_allow_certificate.py @@ -169,4 +169,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['student'] \ No newline at end of file + complete_apps = ['student'] diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 71d2177bd4..54bdd77297 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -107,6 +107,7 @@ class UserProfile(models.Model): TEST_CENTER_STATUS_ACCEPTED = "Accepted" TEST_CENTER_STATUS_ERROR = "Error" + class TestCenterUser(models.Model): """This is our representation of the User for in-person testing, and specifically for Pearson at this point. A few things to note: @@ -190,7 +191,7 @@ class TestCenterUser(models.Model): @staticmethod def user_provided_fields(): - return [ 'first_name', 'middle_name', 'last_name', 'suffix', 'salutation', + return ['first_name', 'middle_name', 'last_name', 'suffix', 'salutation', 'address_1', 'address_2', 'address_3', 'city', 'state', 'postal_code', 'country', 'phone', 'extension', 'phone_country_code', 'fax', 'fax_country_code', 'company_name'] @@ -208,7 +209,7 @@ class TestCenterUser(models.Model): @staticmethod def _generate_edx_id(prefix): NUM_DIGITS = 12 - return u"{}{:012}".format(prefix, randint(1, 10**NUM_DIGITS-1)) + return u"{}{:012}".format(prefix, randint(1, 10 ** NUM_DIGITS - 1)) @staticmethod def _generate_candidate_id(): @@ -237,10 +238,11 @@ class TestCenterUser(models.Model): def is_pending(self): return not self.is_accepted and not self.is_rejected + class TestCenterUserForm(ModelForm): class Meta: model = TestCenterUser - fields = ( 'first_name', 'middle_name', 'last_name', 'suffix', 'salutation', + fields = ('first_name', 'middle_name', 'last_name', 'suffix', 'salutation', 'address_1', 'address_2', 'address_3', 'city', 'state', 'postal_code', 'country', 'phone', 'extension', 'phone_country_code', 'fax', 'fax_country_code', 'company_name') @@ -313,7 +315,8 @@ ACCOMMODATION_CODES = ( ('SRSGNR', 'Separate Room and Sign Language Interpreter'), ) -ACCOMMODATION_CODE_DICT = { code : name for (code, name) in ACCOMMODATION_CODES } +ACCOMMODATION_CODE_DICT = {code: name for (code, name) in ACCOMMODATION_CODES} + class TestCenterRegistration(models.Model): """ @@ -389,10 +392,10 @@ class TestCenterRegistration(models.Model): elif self.uploaded_at is None: return 'Add' elif self.registration_is_rejected: - # Assume that if the registration was rejected before, - # it is more likely this is the (first) correction + # Assume that if the registration was rejected before, + # it is more likely this is the (first) correction # than a second correction in flight before the first was - # processed. + # processed. return 'Add' else: # TODO: decide what to send when we have uploaded an initial version, @@ -417,7 +420,7 @@ class TestCenterRegistration(models.Model): @classmethod def create(cls, testcenter_user, exam, accommodation_request): - registration = cls(testcenter_user = testcenter_user) + registration = cls(testcenter_user=testcenter_user) registration.course_id = exam.course_id registration.accommodation_request = accommodation_request.strip() registration.exam_series_code = exam.exam_series_code @@ -501,7 +504,7 @@ class TestCenterRegistration(models.Model): return self.accommodation_code.split('*') def get_accommodation_names(self): - return [ ACCOMMODATION_CODE_DICT.get(code, "Unknown code " + code) for code in self.get_accommodation_codes() ] + return [ACCOMMODATION_CODE_DICT.get(code, "Unknown code " + code) for code in self.get_accommodation_codes()] @property def registration_signup_url(self): @@ -512,7 +515,7 @@ class TestCenterRegistration(models.Model): return "Accepted" elif self.demographics_is_rejected: return "Rejected" - else: + else: return "Pending" def accommodation_status(self): @@ -522,7 +525,7 @@ class TestCenterRegistration(models.Model): return "Accepted" elif self.accommodation_is_rejected: return "Rejected" - else: + else: return "Pending" def registration_status(self): @@ -532,12 +535,12 @@ class TestCenterRegistration(models.Model): return "Rejected" else: return "Pending" - + class TestCenterRegistrationForm(ModelForm): class Meta: model = TestCenterRegistration - fields = ( 'accommodation_request', 'accommodation_code' ) + fields = ('accommodation_request', 'accommodation_code') def clean_accommodation_request(self): code = self.cleaned_data['accommodation_request'] @@ -576,6 +579,7 @@ def get_testcenter_registration(user, course_id, exam_series_code): # Correct this (https://nose.readthedocs.org/en/latest/finding_tests.html) get_testcenter_registration.__test__ = False + def unique_id_for_user(user): """ Return a unique id for a user, suitable for inserting into @@ -666,6 +670,7 @@ class CourseEnrollmentAllowed(models.Model): #### Helper methods for use from python manage.py shell and other classes. + def get_user_by_username_or_email(username_or_email): """ Return a User object, looking up by email if username_or_email contains a @@ -767,4 +772,3 @@ def update_user_information(sender, instance, created, **kwargs): log = logging.getLogger("mitx.discussion") log.error(unicode(e)) log.error("update user info to discussion failed for user with id: " + str(instance.id)) - diff --git a/common/djangoapps/student/tests.py b/common/djangoapps/student/tests.py index 8ce407bcd1..6a2d75e3d8 100644 --- a/common/djangoapps/student/tests.py +++ b/common/djangoapps/student/tests.py @@ -17,6 +17,7 @@ COURSE_2 = 'edx/full/6.002_Spring_2012' log = logging.getLogger(__name__) + class CourseEndingTest(TestCase): """Test things related to course endings: certificates, surveys, etc""" @@ -40,7 +41,7 @@ class CourseEndingTest(TestCase): {'status': 'processing', 'show_disabled_download_button': False, 'show_download_url': False, - 'show_survey_button': False,}) + 'show_survey_button': False, }) cert_status = {'status': 'unavailable'} self.assertEqual(_cert_info(user, course, cert_status), diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index b583599e97..bf279e7b08 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -50,6 +50,7 @@ from statsd import statsd log = logging.getLogger("mitx.student") Article = namedtuple('Article', 'title url author image deck publication publish_date') + def csrf_token(context): ''' A csrf token that can be included in a form. ''' @@ -73,8 +74,8 @@ def index(request, extra_context={}, user=None): ''' # The course selection work is done in courseware.courses. - domain = settings.MITX_FEATURES.get('FORCE_UNIVERSITY_DOMAIN') # normally False - if domain==False: # do explicit check, because domain=None is valid + domain = settings.MITX_FEATURES.get('FORCE_UNIVERSITY_DOMAIN') # normally False + if domain == False: # do explicit check, because domain=None is valid domain = request.META.get('HTTP_HOST') courses = get_courses(None, domain=domain) @@ -97,6 +98,7 @@ import re day_pattern = re.compile('\s\d+,\s') multimonth_pattern = re.compile('\s?\-\s?\S+\s') + def get_date_for_press(publish_date): import datetime # strip off extra months, and just use the first: @@ -107,6 +109,7 @@ def get_date_for_press(publish_date): date = datetime.datetime.strptime(date, "%B, %Y") return date + def press(request): json_articles = cache.get("student_press_json_articles") if json_articles == None: @@ -148,6 +151,7 @@ def cert_info(user, course): return _cert_info(user, course, certificate_status_for_student(user, course.id)) + def _cert_info(user, course, cert_status): """ Implements the logic for cert_info -- split out for testing. @@ -175,7 +179,7 @@ def _cert_info(user, course, cert_status): d = {'status': status, 'show_download_url': status == 'ready', - 'show_disabled_download_button': status == 'generating',} + 'show_disabled_download_button': status == 'generating', } if (status in ('generating', 'ready', 'notpassing', 'restricted') and course.end_of_course_survey_url is not None): @@ -204,6 +208,7 @@ def _cert_info(user, course, cert_status): return d + @login_required @ensure_csrf_cookie def dashboard(request): @@ -237,9 +242,9 @@ def dashboard(request): show_courseware_links_for = frozenset(course.id for course in courses if has_access(request.user, course, 'load')) - cert_statuses = { course.id: cert_info(request.user, course) for course in courses} + cert_statuses = {course.id: cert_info(request.user, course) for course in courses} - exam_registrations = { course.id: exam_registration_info(request.user, course) for course in courses} + exam_registrations = {course.id: exam_registration_info(request.user, course) for course in courses} # Get the 3 most recent news top_news = _get_news(top=3) @@ -248,7 +253,7 @@ def dashboard(request): 'message': message, 'staff_access': staff_access, 'errored_courses': errored_courses, - 'show_courseware_links_for' : show_courseware_links_for, + 'show_courseware_links_for': show_courseware_links_for, 'cert_statuses': cert_statuses, 'news': top_news, 'exam_registrations': exam_registrations, @@ -312,7 +317,7 @@ def change_enrollment(request): 'error': 'enrollment in {} not allowed at this time' .format(course.display_name)} - org, course_num, run=course_id.split("/") + org, course_num, run = course_id.split("/") statsd.increment("common.student.enrollment", tags=["org:{0}".format(org), "course:{0}".format(course_num), @@ -326,7 +331,7 @@ def change_enrollment(request): enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id) enrollment.delete() - org, course_num, run=course_id.split("/") + org, course_num, run = course_id.split("/") statsd.increment("common.student.unenrollment", tags=["org:{0}".format(org), "course:{0}".format(course_num), @@ -345,7 +350,7 @@ def change_enrollment(request): def accounts_login(request, error=""): - return render_to_response('accounts_login.html', { 'error': error }) + return render_to_response('accounts_login.html', {'error': error}) @@ -424,6 +429,7 @@ def change_setting(request): return HttpResponse(json.dumps({'success': True, 'location': up.location, })) + def _do_create_account(post_vars): """ Given cleaned post variables, create the User and UserProfile objects, as well as the @@ -551,7 +557,7 @@ def create_account(request, post_override=None): # Ok, looks like everything is legit. Create the account. ret = _do_create_account(post_vars) - if isinstance(ret,HttpResponse): # if there was an error then return that + if isinstance(ret, HttpResponse): # if there was an error then return that return ret (user, profile, registration) = ret @@ -591,7 +597,7 @@ def create_account(request, post_override=None): eamap.user = login_user eamap.dtsignup = datetime.datetime.now() eamap.save() - log.debug('Updated ExternalAuthMap for %s to be %s' % (post_vars['username'],eamap)) + log.debug('Updated ExternalAuthMap for %s to be %s' % (post_vars['username'], eamap)) if settings.MITX_FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'): log.debug('bypassing activation email') @@ -603,6 +609,7 @@ def create_account(request, post_override=None): js = {'success': True} return HttpResponse(json.dumps(js), mimetype="application/json") + def exam_registration_info(user, course): """ Returns a Registration object if the user is currently registered for a current exam of the course. Returns None if the user is not registered, or if there is no @@ -620,6 +627,7 @@ def exam_registration_info(user, course): registration = None return registration + @login_required @ensure_csrf_cookie def begin_exam_registration(request, course_id): @@ -663,6 +671,7 @@ def begin_exam_registration(request, course_id): return render_to_response('test_center_register.html', context) + @ensure_csrf_cookie def create_exam_registration(request, post_override=None): ''' @@ -725,7 +734,7 @@ def create_exam_registration(request, post_override=None): # this registration screen. else: - accommodation_request = post_vars.get('accommodation_request','') + accommodation_request = post_vars.get('accommodation_request', '') registration = TestCenterRegistration.create(testcenter_user, exam, accommodation_request) needs_saving = True log.info("User {0} enrolled in course {1} creating new exam registration".format(user.username, course_id)) @@ -834,16 +843,17 @@ def password_reset(request): form = PasswordResetForm(request.POST) if form.is_valid(): - form.save(use_https = request.is_secure(), - from_email = settings.DEFAULT_FROM_EMAIL, - request = request, - domain_override = request.get_host()) - return HttpResponse(json.dumps({'success':True, + form.save(use_https=request.is_secure(), + from_email=settings.DEFAULT_FROM_EMAIL, + request=request, + domain_override=request.get_host()) + return HttpResponse(json.dumps({'success': True, 'value': render_to_string('registration/password_reset_done.html', {})})) else: return HttpResponse(json.dumps({'success': False, 'error': 'Invalid e-mail'})) + @ensure_csrf_cookie def reactivation_email(request): ''' Send an e-mail to reactivate a deactivated account, or to @@ -856,6 +866,7 @@ def reactivation_email(request): 'error': 'No inactive user with this e-mail exists'})) return reactivation_email_for_user(user) + def reactivation_email_for_user(user): reg = Registration.objects.get(user=user) @@ -996,11 +1007,11 @@ def pending_name_changes(request): changes = list(PendingNameChange.objects.all()) js = {'students': [{'new_name': c.new_name, - 'rationale':c.rationale, - 'old_name':UserProfile.objects.get(user=c.user).name, - 'email':c.user.email, - 'uid':c.user.id, - 'cid':c.id} for c in changes]} + 'rationale': c.rationale, + 'old_name': UserProfile.objects.get(user=c.user).name, + 'email': c.user.email, + 'uid': c.user.id, + 'cid': c.id} for c in changes]} return render_to_response('name_changes.html', js) @@ -1057,6 +1068,8 @@ def accept_name_change(request): # TODO: This is a giant kludge to give Pearson something to test against ASAP. # Will need to get replaced by something that actually ties into TestCenterUser + + @csrf_exempt def test_center_login(request): if not settings.MITX_FEATURES.get('ENABLE_PEARSON_HACK_TEST'): diff --git a/common/djangoapps/track/migrations/0001_initial.py b/common/djangoapps/track/migrations/0001_initial.py index 0546203cf8..6ec146dd10 100644 --- a/common/djangoapps/track/migrations/0001_initial.py +++ b/common/djangoapps/track/migrations/0001_initial.py @@ -45,4 +45,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['track'] \ No newline at end of file + complete_apps = ['track'] diff --git a/common/djangoapps/track/migrations/0002_auto__add_field_trackinglog_host__chg_field_trackinglog_event_type__ch.py b/common/djangoapps/track/migrations/0002_auto__add_field_trackinglog_host__chg_field_trackinglog_event_type__ch.py index 4c73aa3bfd..0bb0cde42e 100644 --- a/common/djangoapps/track/migrations/0002_auto__add_field_trackinglog_host__chg_field_trackinglog_event_type__ch.py +++ b/common/djangoapps/track/migrations/0002_auto__add_field_trackinglog_host__chg_field_trackinglog_event_type__ch.py @@ -48,4 +48,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['track'] \ No newline at end of file + complete_apps = ['track'] diff --git a/common/djangoapps/track/models.py b/common/djangoapps/track/models.py index dfdf7a0558..b6a16706c1 100644 --- a/common/djangoapps/track/models.py +++ b/common/djangoapps/track/models.py @@ -2,21 +2,20 @@ from django.db import models from django.db import models + class TrackingLog(models.Model): - dtcreated = models.DateTimeField('creation date',auto_now_add=True) - username = models.CharField(max_length=32,blank=True) - ip = models.CharField(max_length=32,blank=True) + dtcreated = models.DateTimeField('creation date', auto_now_add=True) + username = models.CharField(max_length=32, blank=True) + ip = models.CharField(max_length=32, blank=True) event_source = models.CharField(max_length=32) - event_type = models.CharField(max_length=512,blank=True) + event_type = models.CharField(max_length=512, blank=True) event = models.TextField(blank=True) - agent = models.CharField(max_length=256,blank=True) - page = models.CharField(max_length=512,blank=True,null=True) + agent = models.CharField(max_length=256, blank=True) + page = models.CharField(max_length=512, blank=True, null=True) time = models.DateTimeField('event time') - host = models.CharField(max_length=64,blank=True) + host = models.CharField(max_length=64, blank=True) def __unicode__(self): s = "[%s] %s@%s: %s | %s | %s | %s" % (self.time, self.username, self.ip, self.event_source, self.event_type, self.page, self.event) return s - - diff --git a/common/djangoapps/track/views.py b/common/djangoapps/track/views.py index 54bd476799..ae3a1dcb3e 100644 --- a/common/djangoapps/track/views.py +++ b/common/djangoapps/track/views.py @@ -17,19 +17,21 @@ from track.models import TrackingLog log = logging.getLogger("tracking") -LOGFIELDS = ['username','ip','event_source','event_type','event','agent','page','time','host'] +LOGFIELDS = ['username', 'ip', 'event_source', 'event_type', 'event', 'agent', 'page', 'time', 'host'] + def log_event(event): event_str = json.dumps(event) log.info(event_str[:settings.TRACK_MAX_EVENT]) if settings.MITX_FEATURES.get('ENABLE_SQL_TRACKING_LOGS'): event['time'] = dateutil.parser.parse(event['time']) - tldat = TrackingLog(**dict( (x,event[x]) for x in LOGFIELDS )) + tldat = TrackingLog(**dict((x, event[x]) for x in LOGFIELDS)) try: tldat.save() except Exception as err: log.exception(err) + def user_track(request): try: # TODO: Do the same for many of the optional META parameters username = request.user.username @@ -87,13 +89,14 @@ def server_track(request, event_type, event, page=None): "host": request.META['SERVER_NAME'], } - if event_type.startswith("/event_logs") and request.user.is_staff: # don't log + if event_type.startswith("/event_logs") and request.user.is_staff: # don't log return log_event(event) + @login_required @ensure_csrf_cookie -def view_tracking_log(request,args=''): +def view_tracking_log(request, args=''): if not request.user.is_staff: return redirect('/') nlen = 100 @@ -104,16 +107,15 @@ def view_tracking_log(request,args=''): nlen = int(arg) if arg.startswith('username='): username = arg[9:] - + record_instances = TrackingLog.objects.all().order_by('-time') if username: record_instances = record_instances.filter(username=username) record_instances = record_instances[0:nlen] - + # fix dtstamp fmt = '%a %d-%b-%y %H:%M:%S' # "%Y-%m-%d %H:%M:%S %Z%z" for rinst in record_instances: rinst.dtstr = rinst.time.replace(tzinfo=pytz.utc).astimezone(pytz.timezone('US/Eastern')).strftime(fmt) - return render_to_response('tracking_log.html',{'records':record_instances}) - + return render_to_response('tracking_log.html', {'records': record_instances}) diff --git a/common/djangoapps/util/cache.py b/common/djangoapps/util/cache.py index 89b5dffd5e..8ab1b06acd 100644 --- a/common/djangoapps/util/cache.py +++ b/common/djangoapps/util/cache.py @@ -58,4 +58,3 @@ def cache_if_anonymous(view_func): return view_func(request, *args, **kwargs) return _decorated - diff --git a/common/djangoapps/util/converters.py b/common/djangoapps/util/converters.py index 7f96dc6c30..900371a0dd 100644 --- a/common/djangoapps/util/converters.py +++ b/common/djangoapps/util/converters.py @@ -1,7 +1,9 @@ -import time, datetime +import time +import datetime import re import calendar + def time_to_date(time_obj): """ Convert a time.time_struct to a true universal time (can pass to js Date constructor) @@ -9,6 +11,7 @@ def time_to_date(time_obj): # TODO change to using the isoformat() function on datetime. js date can parse those return calendar.timegm(time_obj) * 1000 + def jsdate_to_time(field): """ Convert a universal time (iso format) or msec since epoch to a time obj @@ -16,9 +19,9 @@ def jsdate_to_time(field): if field is None: return field elif isinstance(field, basestring): # iso format but ignores time zone assuming it's Z - d=datetime.datetime(*map(int, re.split('[^\d]', field)[:6])) # stop after seconds. Debatable + d = datetime.datetime(*map(int, re.split('[^\d]', field)[:6])) # stop after seconds. Debatable return d.utctimetuple() elif isinstance(field, int) or isinstance(field, float): return time.gmtime(field / 1000) elif isinstance(field, time.struct_time): - return field \ No newline at end of file + return field diff --git a/common/djangoapps/util/json_request.py b/common/djangoapps/util/json_request.py index 4beff7bdc8..840a8282f9 100644 --- a/common/djangoapps/util/json_request.py +++ b/common/djangoapps/util/json_request.py @@ -13,7 +13,7 @@ def expect_json(view_function): def expect_json_with_cloned_request(request, *args, **kwargs): # cdodge: fix postback errors in CMS. The POST 'content-type' header can include additional information # e.g. 'charset', so we can't do a direct string compare - if request.META.get('CONTENT_TYPE','').lower().startswith("application/json"): + if request.META.get('CONTENT_TYPE', '').lower().startswith("application/json"): cloned_request = copy.copy(request) cloned_request.POST = cloned_request.POST.copy() cloned_request.POST.update(json.loads(request.body)) diff --git a/common/djangoapps/util/views.py b/common/djangoapps/util/views.py index 0ccdd03301..cece37757b 100644 --- a/common/djangoapps/util/views.py +++ b/common/djangoapps/util/views.py @@ -93,6 +93,7 @@ def accepts(request, media_type): accept = parse_accept_header(request.META.get("HTTP_ACCEPT", "")) return media_type in [t for (t, p, q) in accept] + def debug_request(request): """Return a pretty printed version of the request""" diff --git a/common/djangoapps/xmodule_modifiers.py b/common/djangoapps/xmodule_modifiers.py index b171b402ee..7b19c27553 100644 --- a/common/djangoapps/xmodule_modifiers.py +++ b/common/djangoapps/xmodule_modifiers.py @@ -12,6 +12,7 @@ from xmodule.vertical_module import VerticalModule log = logging.getLogger("mitx.xmodule_modifiers") + def wrap_xmodule(get_html, module, template, context=None): """ Wraps the results of get_html in a standard
            with identifying @@ -32,7 +33,7 @@ def wrap_xmodule(get_html, module, template, context=None): def _get_html(): context.update({ 'content': get_html(), - 'display_name' : module.metadata.get('display_name') if module.metadata is not None else None, + 'display_name': module.metadata.get('display_name') if module.metadata is not None else None, 'class_': module.__class__.__name__, 'module_name': module.js_module_name }) @@ -52,6 +53,7 @@ def replace_course_urls(get_html, course_id): return static_replace.replace_course_urls(get_html(), course_id) return _get_html + def replace_static_urls(get_html, data_dir, course_namespace=None): """ Updates the supplied module with a new get_html function that wraps @@ -64,6 +66,7 @@ def replace_static_urls(get_html, data_dir, course_namespace=None): return static_replace.replace_static_urls(get_html(), data_dir, course_namespace) return _get_html + def grade_histogram(module_id): ''' Print out a histogram of grades on a given problem. Part of staff member debug info. @@ -98,7 +101,7 @@ def add_histogram(get_html, module, user): @wraps(get_html) def _get_html(): - if type(module) in [SequenceModule, VerticalModule]: # TODO: make this more general, eg use an XModule attribute instead + if type(module) in [SequenceModule, VerticalModule]: # TODO: make this more general, eg use an XModule attribute instead return get_html() module_id = module.id @@ -114,35 +117,35 @@ def add_histogram(get_html, module, user): # doesn't like symlinks) filepath = filename data_dir = osfs.root_path.rsplit('/')[-1] - giturl = module.metadata.get('giturl','https://github.com/MITx') - edit_link = "%s/%s/tree/master/%s" % (giturl,data_dir,filepath) + giturl = module.metadata.get('giturl', 'https://github.com/MITx') + edit_link = "%s/%s/tree/master/%s" % (giturl, data_dir, filepath) else: edit_link = False # Need to define all the variables that are about to be used giturl = "" data_dir = "" - source_file = module.metadata.get('source_file','') # source used to generate the problem XML, eg latex or word + source_file = module.metadata.get('source_file', '') # source used to generate the problem XML, eg latex or word # useful to indicate to staff if problem has been released or not # TODO (ichuang): use _has_access_descriptor.can_load in lms.courseware.access, instead of now>mstart comparison here now = time.gmtime() is_released = "unknown" - mstart = getattr(module.descriptor,'start') + mstart = getattr(module.descriptor, 'start') if mstart is not None: is_released = "Yes!" if (now > mstart) else "Not yet" staff_context = {'definition': module.definition.get('data'), 'metadata': json.dumps(module.metadata, indent=4), 'location': module.location, - 'xqa_key': module.metadata.get('xqa_key',''), - 'source_file' : source_file, - 'source_url': '%s/%s/tree/master/%s' % (giturl,data_dir,source_file), + 'xqa_key': module.metadata.get('xqa_key', ''), + 'source_file': source_file, + 'source_url': '%s/%s/tree/master/%s' % (giturl, data_dir, source_file), 'category': str(module.__class__.__name__), # Template uses element_id in js function names, so can't allow dashes - 'element_id': module.location.html_id().replace('-','_'), + 'element_id': module.location.html_id().replace('-', '_'), 'edit_link': edit_link, 'user': user, - 'xqa_server' : settings.MITX_FEATURES.get('USE_XQA_SERVER','http://xqa:server@content-qa.mitx.mit.edu/xqa'), + 'xqa_server': settings.MITX_FEATURES.get('USE_XQA_SERVER', 'http://xqa:server@content-qa.mitx.mit.edu/xqa'), 'histogram': json.dumps(histogram), 'render_histogram': render_histogram, 'module_content': get_html(), @@ -151,4 +154,3 @@ def add_histogram(get_html, module, user): return render_to_string("staff_problem_info.html", staff_context) return _get_html - diff --git a/common/lib/capa/capa/calc.py b/common/lib/capa/capa/calc.py index 40ac14308e..0f062d17d5 100644 --- a/common/lib/capa/capa/calc.py +++ b/common/lib/capa/capa/calc.py @@ -121,9 +121,9 @@ def evaluator(variables, functions, string, cs=False): # confusing. They may also conflict with variables if we ever allow e.g. # 5R instead of 5*R suffixes = {'%': 0.01, 'k': 1e3, 'M': 1e6, 'G': 1e9, - 'T': 1e12,# 'P':1e15,'E':1e18,'Z':1e21,'Y':1e24, + 'T': 1e12, # 'P':1e15,'E':1e18,'Z':1e21,'Y':1e24, 'c': 1e-2, 'm': 1e-3, 'u': 1e-6, - 'n': 1e-9, 'p': 1e-12}# ,'f':1e-15,'a':1e-18,'z':1e-21,'y':1e-24} + 'n': 1e-9, 'p': 1e-12} # ,'f':1e-15,'a':1e-18,'z':1e-21,'y':1e-24} def super_float(text): ''' Like float, but with si extensions. 1k goes to 1000''' diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index 4b0faa91a1..9b8bbd7288 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -75,7 +75,7 @@ global_context = {'random': random, 'draganddrop': verifiers.draganddrop} # These should be removed from HTML output, including all subelements -html_problem_semantics = ["codeparam", "responseparam", "answer", "script", "hintgroup", "openendedparam","openendedrubric"] +html_problem_semantics = ["codeparam", "responseparam", "answer", "script", "hintgroup", "openendedparam", "openendedrubric"] log = logging.getLogger('mitx.' + __name__) @@ -453,7 +453,7 @@ class LoncapaProblem(object): exec code in context, context except Exception as err: log.exception("Error while execing script code: " + code) - msg = "Error while executing script code: %s" % str(err).replace('<','<') + msg = "Error while executing script code: %s" % str(err).replace('<', '<') raise responsetypes.LoncapaProblemError(msg) finally: sys.path = original_path @@ -502,7 +502,7 @@ class LoncapaProblem(object): 'id': problemtree.get('id'), 'feedback': {'message': msg, 'hint': hint, - 'hintmode': hintmode,}} + 'hintmode': hintmode, }} input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag) the_input = input_type_cls(self.system, problemtree, state) diff --git a/common/lib/capa/capa/chem/__init__.py b/common/lib/capa/capa/chem/__init__.py index 8b13789179..e69de29bb2 100644 --- a/common/lib/capa/capa/chem/__init__.py +++ b/common/lib/capa/capa/chem/__init__.py @@ -1 +0,0 @@ - diff --git a/common/lib/capa/capa/chem/chemcalc.py b/common/lib/capa/capa/chem/chemcalc.py index 389e688cf4..5b80005044 100644 --- a/common/lib/capa/capa/chem/chemcalc.py +++ b/common/lib/capa/capa/chem/chemcalc.py @@ -17,17 +17,17 @@ from nltk.tree import Tree ARROWS = ('<->', '->') ## Defines a simple pyparsing tokenizer for chemical equations -elements = ['Ac','Ag','Al','Am','Ar','As','At','Au','B','Ba','Be', - 'Bh','Bi','Bk','Br','C','Ca','Cd','Ce','Cf','Cl','Cm', - 'Cn','Co','Cr','Cs','Cu','Db','Ds','Dy','Er','Es','Eu', - 'F','Fe','Fl','Fm','Fr','Ga','Gd','Ge','H','He','Hf', - 'Hg','Ho','Hs','I','In','Ir','K','Kr','La','Li','Lr', - 'Lu','Lv','Md','Mg','Mn','Mo','Mt','N','Na','Nb','Nd', - 'Ne','Ni','No','Np','O','Os','P','Pa','Pb','Pd','Pm', - 'Po','Pr','Pt','Pu','Ra','Rb','Re','Rf','Rg','Rh','Rn', - 'Ru','S','Sb','Sc','Se','Sg','Si','Sm','Sn','Sr','Ta', - 'Tb','Tc','Te','Th','Ti','Tl','Tm','U','Uuo','Uup', - 'Uus','Uut','V','W','Xe','Y','Yb','Zn','Zr'] +elements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', + 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', + 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', + 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', + 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', + 'Lu', 'Lv', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', + 'Ne', 'Ni', 'No', 'Np', 'O', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', + 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', + 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', + 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'U', 'Uuo', 'Uup', + 'Uus', 'Uut', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'] digits = map(str, range(10)) symbols = list("[](){}^+-/") phases = ["(s)", "(l)", "(g)", "(aq)"] @@ -252,7 +252,7 @@ def _get_final_tree(s): ''' tokenized = tokenizer.parseString(s) parsed = parser.parse(tokenized) - merged = _merge_children(parsed, {'S','group'}) + merged = _merge_children(parsed, {'S', 'group'}) final = _clean_parse_tree(merged) return final diff --git a/common/lib/capa/capa/correctmap.py b/common/lib/capa/capa/correctmap.py index c7386219b1..a78b10d07a 100644 --- a/common/lib/capa/capa/correctmap.py +++ b/common/lib/capa/capa/correctmap.py @@ -3,6 +3,7 @@ # # Used by responsetypes and capa_problem + class CorrectMap(object): """ Stores map between answer_id and response evaluation result for each question @@ -152,6 +153,3 @@ class CorrectMap(object): if not isinstance(other_cmap, CorrectMap): raise Exception('CorrectMap.update called with invalid argument %s' % other_cmap) self.cmap.update(other_cmap.get_dict()) - - - diff --git a/common/lib/capa/capa/customrender.py b/common/lib/capa/capa/customrender.py index ef1044e8b1..a925a5970d 100644 --- a/common/lib/capa/capa/customrender.py +++ b/common/lib/capa/capa/customrender.py @@ -22,6 +22,8 @@ log = logging.getLogger('mitx.' + __name__) registry = TagRegistry() #----------------------------------------------------------------------------- + + class MathRenderer(object): tags = ['math'] @@ -77,6 +79,7 @@ registry.register(MathRenderer) #----------------------------------------------------------------------------- + class SolutionRenderer(object): ''' A solution is just a ... which is given an ID, that is used for displaying an @@ -97,4 +100,3 @@ class SolutionRenderer(object): return etree.XML(html) registry.register(SolutionRenderer) - diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 0b0e86ce66..83c79a7247 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -54,6 +54,7 @@ log = logging.getLogger('mitx.' + __name__) registry = TagRegistry() + class Attribute(object): """ Allows specifying required and optional attributes for input types. @@ -413,7 +414,7 @@ class JavascriptInput(InputTypeBase): return [Attribute('params', None), Attribute('problem_state', None), Attribute('display_class', None), - Attribute('display_file', None),] + Attribute('display_file', None), ] def setup(self): @@ -477,12 +478,13 @@ class TextLine(InputTypeBase): def _extra_context(self): return {'do_math': self.do_math, - 'preprocessor': self.preprocessor,} + 'preprocessor': self.preprocessor, } registry.register(TextLine) #----------------------------------------------------------------------------- + class FileSubmission(InputTypeBase): """ Upload some files (e.g. for programming assignments) @@ -508,7 +510,7 @@ class FileSubmission(InputTypeBase): Convert the list of allowed files to a convenient format. """ return [Attribute('allowed_files', '[]', transform=cls.parse_files), - Attribute('required_files', '[]', transform=cls.parse_files),] + Attribute('required_files', '[]', transform=cls.parse_files), ] def setup(self): """ @@ -524,7 +526,7 @@ class FileSubmission(InputTypeBase): self.msg = FileSubmission.submitted_msg def _extra_context(self): - return {'queue_len': self.queue_len,} + return {'queue_len': self.queue_len, } return context registry.register(FileSubmission) @@ -582,7 +584,7 @@ class CodeInput(InputTypeBase): def _extra_context(self): """Defined queue_len, add it """ - return {'queue_len': self.queue_len,} + return {'queue_len': self.queue_len, } registry.register(CodeInput) @@ -606,7 +608,7 @@ class Schematic(InputTypeBase): Attribute('parts', None), Attribute('analyses', None), Attribute('initial_value', None), - Attribute('submit_analyses', None),] + Attribute('submit_analyses', None), ] return context @@ -614,6 +616,7 @@ registry.register(Schematic) #----------------------------------------------------------------------------- + class ImageInput(InputTypeBase): """ Clickable image as an input field. Element should specify the image source, height, @@ -635,7 +638,7 @@ class ImageInput(InputTypeBase): """ return [Attribute('src'), Attribute('height'), - Attribute('width'),] + Attribute('width'), ] def setup(self): @@ -660,6 +663,7 @@ registry.register(ImageInput) #----------------------------------------------------------------------------- + class Crystallography(InputTypeBase): """ An input for crystallography -- user selects 3 points on the axes, and we get a plane. @@ -728,18 +732,19 @@ class ChemicalEquationInput(InputTypeBase): """ Can set size of text field. """ - return [Attribute('size', '20'),] + return [Attribute('size', '20'), ] def _extra_context(self): """ TODO (vshnayder): Get rid of this once we have a standard way of requiring js to be loaded. """ - return {'previewer': '/static/js/capa/chemical_equation_preview.js',} + return {'previewer': '/static/js/capa/chemical_equation_preview.js', } registry.register(ChemicalEquationInput) #----------------------------------------------------------------------------- + class DragAndDropInput(InputTypeBase): """ Input for drag and drop problems. Allows student to drag and drop images and diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 5f0e1639b2..adf5eda416 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -186,9 +186,9 @@ class LoncapaResponse(object): tree = etree.Element('span') # problem author can make this span display:inline - if self.xml.get('inline',''): - tree.set('class','inline') - + if self.xml.get('inline', ''): + tree.set('class', 'inline') + for item in self.xml: # call provided procedure to do the rendering item_xhtml = renderer(item) @@ -1294,7 +1294,7 @@ class CodeResponse(LoncapaResponse): # State associated with the queueing request queuestate = {'key': queuekey, - 'time': qtime,} + 'time': qtime, } cmap = CorrectMap() if error: diff --git a/common/lib/capa/capa/tests/__init__.py b/common/lib/capa/capa/tests/__init__.py index b06975f6ce..89cb5a5ee9 100644 --- a/common/lib/capa/capa/tests/__init__.py +++ b/common/lib/capa/capa/tests/__init__.py @@ -8,6 +8,7 @@ import xml.sax.saxutils as saxutils TEST_DIR = os.path.dirname(os.path.realpath(__file__)) + def tst_render_template(template, context): """ A test version of render to template. Renders to the repr of the context, completely ignoring @@ -25,7 +26,7 @@ test_system = Mock( user=Mock(), filestore=fs.osfs.OSFS(os.path.join(TEST_DIR, "test_files")), debug=True, - xqueue={'interface':None, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 10}, + xqueue={'interface': None, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 10}, node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"), - anonymous_student_id = 'student' + anonymous_student_id='student' ) diff --git a/common/lib/capa/capa/tests/test_customrender.py b/common/lib/capa/capa/tests/test_customrender.py index 7208ab2941..eece275b05 100644 --- a/common/lib/capa/capa/tests/test_customrender.py +++ b/common/lib/capa/capa/tests/test_customrender.py @@ -8,6 +8,7 @@ from capa import customrender # just a handy shortcut lookup_tag = customrender.registry.get_class_for_tag + def extract_context(xml): """ Given an xml element corresponding to the output of test_system.render_template, get back the @@ -15,9 +16,11 @@ def extract_context(xml): """ return eval(xml.text) + def quote_attr(s): return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes + class HelperTest(unittest.TestCase): ''' Make sure that our helper function works! @@ -50,7 +53,7 @@ class SolutionRenderTest(unittest.TestCase): # our test_system "renders" templates to a div with the repr of the context xml = renderer.get_html() context = extract_context(xml) - self.assertEqual(context, {'id' : 'solution_12'}) + self.assertEqual(context, {'id': 'solution_12'}) class MathRenderTest(unittest.TestCase): @@ -65,12 +68,11 @@ class MathRenderTest(unittest.TestCase): renderer = lookup_tag('math')(test_system, element) self.assertEqual(renderer.mathstr, mathjax_out) - + def test_parsing(self): self.check_parse('$abc$', '[mathjaxinline]abc[/mathjaxinline]') self.check_parse('$abc', '$abc') self.check_parse(r'$\displaystyle 2+2$', '[mathjax] 2+2[/mathjax]') - + # NOTE: not testing get_html yet because I don't understand why it's doing what it's doing. - diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 6c282baf95..4a5ea5c429 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -31,6 +31,7 @@ lookup_tag = inputtypes.registry.get_class_for_tag def quote_attr(s): return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes + class OptionInputTest(unittest.TestCase): ''' Make sure option inputs work @@ -100,7 +101,7 @@ class ChoiceGroupTest(unittest.TestCase): 'input_type': expected_input_type, 'choices': [('foil1', 'This is foil One.'), ('foil2', 'This is foil Two.'), - ('foil3', 'This is foil Three.'),], + ('foil3', 'This is foil Three.'), ], 'name_array_suffix': expected_suffix, # what is this for?? } @@ -137,7 +138,7 @@ class JavascriptInputTest(unittest.TestCase): element = etree.fromstring(xml_str) - state = {'value': '3',} + state = {'value': '3', } the_input = lookup_tag('javascriptinput')(test_system, element, state) context = the_input._get_render_context() @@ -149,7 +150,7 @@ class JavascriptInputTest(unittest.TestCase): 'params': params, 'display_file': display_file, 'display_class': display_class, - 'problem_state': problem_state,} + 'problem_state': problem_state, } self.assertEqual(context, expected) @@ -165,7 +166,7 @@ class TextLineTest(unittest.TestCase): element = etree.fromstring(xml_str) - state = {'value': 'BumbleBee',} + state = {'value': 'BumbleBee', } the_input = lookup_tag('textline')(test_system, element, state) context = the_input._get_render_context() @@ -193,7 +194,7 @@ class TextLineTest(unittest.TestCase): element = etree.fromstring(xml_str) - state = {'value': 'BumbleBee',} + state = {'value': 'BumbleBee', } the_input = lookup_tag('textline')(test_system, element, state) context = the_input._get_render_context() @@ -231,7 +232,7 @@ class FileSubmissionTest(unittest.TestCase): state = {'value': 'BumbleBee.py', 'status': 'incomplete', - 'feedback' : {'message': '3'}, } + 'feedback': {'message': '3'}, } input_class = lookup_tag('filesubmission') the_input = input_class(test_system, element, state) @@ -275,7 +276,7 @@ class CodeInputTest(unittest.TestCase): state = {'value': 'print "good evening"', 'status': 'incomplete', - 'feedback' : {'message': '3'}, } + 'feedback': {'message': '3'}, } input_class = lookup_tag('codeinput') the_input = input_class(test_system, element, state) @@ -488,7 +489,7 @@ class ChemicalEquationTest(unittest.TestCase): element = etree.fromstring(xml_str) - state = {'value': 'H2OYeah',} + state = {'value': 'H2OYeah', } the_input = lookup_tag('chemicalequationinput')(test_system, element, state) context = the_input._get_render_context() diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 9eecef3986..18da338b91 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -16,6 +16,7 @@ from capa.correctmap import CorrectMap from capa.util import convert_files_to_filenames from capa.xqueue_interface import dateformat + class MultiChoiceTest(unittest.TestCase): def test_MC_grade(self): multichoice_file = os.path.dirname(__file__) + "/test_files/multichoice.xml" @@ -295,16 +296,16 @@ class CodeResponseTest(unittest.TestCase): old_cmap = CorrectMap() for i, answer_id in enumerate(answer_ids): queuekey = 1000 + i - queuestate = CodeResponseTest.make_queuestate(1000+i, datetime.now()) + queuestate = CodeResponseTest.make_queuestate(1000 + i, datetime.now()) old_cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=queuestate)) # Message format common to external graders - grader_msg = 'MESSAGE' # Must be valid XML - correct_score_msg = json.dumps({'correct':True, 'score':1, 'msg': grader_msg}) - incorrect_score_msg = json.dumps({'correct':False, 'score':0, 'msg': grader_msg}) + grader_msg = 'MESSAGE' # Must be valid XML + correct_score_msg = json.dumps({'correct': True, 'score': 1, 'msg': grader_msg}) + incorrect_score_msg = json.dumps({'correct': False, 'score': 0, 'msg': grader_msg}) xserver_msgs = {'correct': correct_score_msg, - 'incorrect': incorrect_score_msg,} + 'incorrect': incorrect_score_msg, } # Incorrect queuekey, state should not be updated for correctness in ['correct', 'incorrect']: @@ -325,7 +326,7 @@ class CodeResponseTest(unittest.TestCase): new_cmap = CorrectMap() new_cmap.update(old_cmap) - npoints = 1 if correctness=='correct' else 0 + npoints = 1 if correctness == 'correct' else 0 new_cmap.set(answer_id=answer_id, npoints=npoints, correctness=correctness, msg=grader_msg, queuestate=None) test_lcp.update_score(xserver_msgs[correctness], queuekey=1000 + i) @@ -361,7 +362,7 @@ class CodeResponseTest(unittest.TestCase): for i, answer_id in enumerate(answer_ids): queuekey = 1000 + i latest_timestamp = datetime.now() - queuestate = CodeResponseTest.make_queuestate(1000+i, latest_timestamp) + queuestate = CodeResponseTest.make_queuestate(1000 + i, latest_timestamp) cmap.update(CorrectMap(answer_id=answer_id, queuestate=queuestate)) test_lcp.correct_map.update(cmap) @@ -412,6 +413,7 @@ class ChoiceResponseTest(unittest.TestCase): self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_1'), 'incorrect') self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_4_1'), 'correct') + class JavascriptResponseTest(unittest.TestCase): def test_jr_grade(self): @@ -424,4 +426,3 @@ class JavascriptResponseTest(unittest.TestCase): self.assertEquals(test_lcp.grade_answers(incorrect_answers).get_correctness('1_2_1'), 'incorrect') self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct') - diff --git a/common/lib/capa/capa/util.py b/common/lib/capa/capa/util.py index 0df58c216f..a0f25c4947 100644 --- a/common/lib/capa/capa/util.py +++ b/common/lib/capa/capa/util.py @@ -51,15 +51,17 @@ def convert_files_to_filenames(answers): new_answers = dict() for answer_id in answers.keys(): answer = answers[answer_id] - if is_list_of_files(answer): # Files are stored as a list, even if one file + if is_list_of_files(answer): # Files are stored as a list, even if one file new_answers[answer_id] = [f.name for f in answer] else: new_answers[answer_id] = answers[answer_id] return new_answers + def is_list_of_files(files): return isinstance(files, list) and all(is_file(f) for f in files) + def is_file(file_to_test): ''' Duck typing to check if 'file_to_test' is a File object @@ -79,11 +81,10 @@ def find_with_default(node, path, default): Returns: node.find(path).text if the find succeeds, default otherwise. - + """ v = node.find(path) if v is not None: return v.text else: return default - diff --git a/common/lib/capa/capa/xqueue_interface.py b/common/lib/capa/capa/xqueue_interface.py index 798867955b..8dbe2c84aa 100644 --- a/common/lib/capa/capa/xqueue_interface.py +++ b/common/lib/capa/capa/xqueue_interface.py @@ -10,6 +10,7 @@ import requests log = logging.getLogger('mitx.' + __name__) dateformat = '%Y%m%d%H%M%S' + def make_hashkey(seed): ''' Generate a string key by hashing @@ -29,9 +30,9 @@ def make_xheader(lms_callback_url, lms_key, queue_name): 'queue_name': designate a specific queue within xqueue server, e.g. 'MITx-6.00x' (string) } """ - return json.dumps({ 'lms_callback_url': lms_callback_url, + return json.dumps({'lms_callback_url': lms_callback_url, 'lms_key': lms_key, - 'queue_name': queue_name }) + 'queue_name': queue_name}) def parse_xreply(xreply): @@ -96,18 +97,18 @@ class XQueueInterface(object): def _login(self): - payload = { 'username': self.auth['username'], - 'password': self.auth['password'] } + payload = {'username': self.auth['username'], + 'password': self.auth['password']} return self._http_post(self.url + '/xqueue/login/', payload) def _send_to_queue(self, header, body, files_to_upload): payload = {'xqueue_header': header, - 'xqueue_body' : body} + 'xqueue_body': body} files = {} if files_to_upload is not None: for f in files_to_upload: - files.update({ f.name: f }) + files.update({f.name: f}) return self._http_post(self.url + '/xqueue/submit/', payload, files=files) diff --git a/common/lib/supertrace.py b/common/lib/supertrace.py index e17cd7a8ba..83dfa12031 100644 --- a/common/lib/supertrace.py +++ b/common/lib/supertrace.py @@ -3,7 +3,8 @@ A handy util to print a django-debug-screen-like stack trace with values of local variables. """ -import sys, traceback +import sys +import traceback from django.utils.encoding import smart_unicode @@ -48,5 +49,3 @@ def supertrace(max_len=160): print s except: print "" - - diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index 817af9c10d..3bc8bc5143 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -30,7 +30,7 @@ setup( "peergrading = xmodule.peer_grading_module:PeerGradingDescriptor", "problem = xmodule.capa_module:CapaDescriptor", "problemset = xmodule.seq_module:SequenceDescriptor", - "randomize = xmodule.randomize_module:RandomizeDescriptor", + "randomize = xmodule.randomize_module:RandomizeDescriptor", "section = xmodule.backcompat_module:SemanticSectionDescriptor", "sequential = xmodule.seq_module:SequenceDescriptor", "slides = xmodule.backcompat_module:TranslateCustomTagDescriptor", diff --git a/common/lib/xmodule/xmodule/abtest_module.py b/common/lib/xmodule/xmodule/abtest_module.py index 12456bc7d7..537d864127 100644 --- a/common/lib/xmodule/xmodule/abtest_module.py +++ b/common/lib/xmodule/xmodule/abtest_module.py @@ -51,7 +51,7 @@ class ABTestModule(XModule): def get_shared_state(self): return json.dumps({'group': self.group}) - + def get_child_descriptors(self): active_locations = set(self.definition['data']['group_content'][self.group]) return [desc for desc in self.descriptor.get_children() if desc.location.url() in active_locations] @@ -171,7 +171,7 @@ class ABTestDescriptor(RawDescriptor, XmlDescriptor): group_elem.append(etree.fromstring(child.export_to_xml(resource_fs))) return xml_object - - + + def has_dynamic_children(self): return True diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index d3c8786f66..d806ec7913 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -29,6 +29,7 @@ TIMEDELTA_REGEX = re.compile(r'^((?P\d+?) day(?:s?))?(\s)?((?P\d+?) # Generated this many different variants of problems with rerandomize=per_student NUM_RANDOMIZATION_BINS = 20 + def randomization_bin(seed, problem_id): """ Pick a randomization bin for the problem given the user's seed and a problem id. @@ -43,6 +44,7 @@ def randomization_bin(seed, problem_id): # get the first few digits of the hash, convert to an int, then mod. return int(h.hexdigest()[:7], 16) % NUM_RANDOMIZATION_BINS + def only_one(lst, default="", process=lambda x: x): """ If lst is empty, returns default @@ -283,7 +285,7 @@ class CapaModule(XModule): # Next, generate a fresh LoncapaProblem self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), - state=None, # Tabula rasa + state=None, # Tabula rasa seed=self.seed, system=self.system) # Prepend a scary warning to the student @@ -302,7 +304,7 @@ class CapaModule(XModule): html = warning try: html += self.lcp.get_html() - except Exception, err: # Couldn't do it. Give up + except Exception, err: # Couldn't do it. Give up log.exception(err) raise @@ -315,7 +317,7 @@ class CapaModule(XModule): # check button is context-specific. # Put a "Check" button if unlimited attempts or still some left - if self.max_attempts is None or self.attempts < self.max_attempts-1: + if self.max_attempts is None or self.attempts < self.max_attempts - 1: check_button = "Check" else: # Will be final check so let user know that @@ -561,9 +563,9 @@ class CapaModule(XModule): current_time = datetime.datetime.now() prev_submit_time = self.lcp.get_recentmost_queuetime() waittime_between_requests = self.system.xqueue['waittime'] - if (current_time-prev_submit_time).total_seconds() < waittime_between_requests: + if (current_time - prev_submit_time).total_seconds() < waittime_between_requests: msg = 'You must wait at least %d seconds between submissions' % waittime_between_requests - return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback + return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback try: old_state = self.lcp.get_state() @@ -596,7 +598,7 @@ class CapaModule(XModule): event_info['attempts'] = self.attempts self.system.track_function('save_problem_check', event_info) - if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback + if hasattr(self.system, 'psychometrics_handler'): # update PsychometricsData using callback self.system.psychometrics_handler(self.get_instance_state()) # render problem into HTML @@ -707,7 +709,7 @@ class CapaDescriptor(RawDescriptor): @property def editable_metadata_fields(self): """Remove metadata from the editable fields since it has its own editor""" - subset = super(CapaDescriptor,self).editable_metadata_fields + subset = super(CapaDescriptor, self).editable_metadata_fields if 'markdown' in subset: subset.remove('markdown') return subset diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 14a59c9004..112a7a68f8 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -50,14 +50,16 @@ ACCEPT_FILE_UPLOAD = False TRUE_DICT = ["True", True, "TRUE", "true"] HUMAN_TASK_TYPE = { - 'selfassessment' : "Self Assessment", - 'openended' : "External Grader", + 'selfassessment': "Self Assessment", + 'openended': "External Grader", } + class IncorrectMaxScoreError(Exception): def __init__(self, msg): self.msg = msg + class CombinedOpenEndedModule(XModule): """ This is a module that encapsulates all open ended grading (self assessment, peer assessment, etc). @@ -700,4 +702,4 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): for child in ['task']: add_child(child) - return elt \ No newline at end of file + return elt diff --git a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py index 6d4a3eebdf..9a213299cd 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py @@ -1,12 +1,14 @@ import logging from lxml import etree -log=logging.getLogger(__name__) +log = logging.getLogger(__name__) + class RubricParsingError(Exception): def __init__(self, msg): self.msg = msg + class CombinedOpenEndedRubric(object): def __init__ (self, system, view_only = False): @@ -27,8 +29,8 @@ class CombinedOpenEndedRubric(object): success = False try: rubric_categories = self.extract_categories(rubric_xml) - html = self.system.render_template('open_ended_rubric.html', - {'categories' : rubric_categories, + html = self.system.render_template('open_ended_rubric.html', + {'categories': rubric_categories, 'has_score': self.has_score, 'view_only': self.view_only}) success = True @@ -60,8 +62,8 @@ class CombinedOpenEndedRubric(object): options: [{text: "Option 1 Name", points: 0}, {text:"Option 2 Name", points: 5}] }, { category: "Category 2 Name", - options: [{text: "Option 1 Name", points: 0}, - {text: "Option 2 Name", points: 1}, + options: [{text: "Option 1 Name", points: 0}, + {text: "Option 2 Name", points: 1}, {text: "Option 3 Name", points: 2]}] ''' @@ -77,7 +79,7 @@ class CombinedOpenEndedRubric(object): def extract_category(self, category): - ''' + ''' construct an individual category {category: "Category 1 Name", options: [{text: "Option 1 text", points: 1}, @@ -110,7 +112,7 @@ class CombinedOpenEndedRubric(object): autonumbering = True # parse options for option in optionsxml: - if option.tag != 'option': + if option.tag != 'option': raise RubricParsingError("[extract_category]: expected option tag, got {0} instead".format(option.tag)) else: pointstr = option.get("points") @@ -127,7 +129,7 @@ class CombinedOpenEndedRubric(object): cur_points = cur_points + 1 else: raise Exception("[extract_category]: missing points attribute. Cannot continue to auto-create points values after a points value is explicitly defined.") - + selected = score == points optiontext = option.text options.append({'text': option.text, 'points': points, 'selected': selected}) diff --git a/common/lib/xmodule/xmodule/conditional_module.py b/common/lib/xmodule/xmodule/conditional_module.py index e20681e614..bcdf0f4738 100644 --- a/common/lib/xmodule/xmodule/conditional_module.py +++ b/common/lib/xmodule/xmodule/conditional_module.py @@ -9,12 +9,13 @@ from pkg_resources import resource_string log = logging.getLogger('mitx.' + __name__) + class ConditionalModule(XModule): ''' Blocks child module from showing unless certain conditions are met. Example: - + @@ -37,13 +38,13 @@ class ConditionalModule(XModule): def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs): """ In addition to the normal XModule init, provide: - + self.condition = string describing condition required """ XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs) self.contents = None - self.condition = self.metadata.get('condition','') + self.condition = self.metadata.get('condition', '') #log.debug('conditional module required=%s' % self.required_modules_list) def _get_required_modules(self): @@ -56,7 +57,7 @@ class ConditionalModule(XModule): def is_condition_satisfied(self): self._get_required_modules() - if self.condition=='require_completed': + if self.condition == 'require_completed': # all required modules must be completed, as determined by # the modules .is_completed() method for module in self.required_modules: @@ -70,7 +71,7 @@ class ConditionalModule(XModule): else: log.debug('conditional module: %s IS completed' % module) return True - elif self.condition=='require_attempted': + elif self.condition == 'require_attempted': # all required modules must be attempted, as determined by # the modules .is_attempted() method for module in self.required_modules: @@ -111,9 +112,10 @@ class ConditionalModule(XModule): # for now, just deal with one child html = self.contents[0] - + return json.dumps({'html': html}) + class ConditionalDescriptor(SequenceDescriptor): module_class = ConditionalModule @@ -125,7 +127,7 @@ class ConditionalDescriptor(SequenceDescriptor): def __init__(self, *args, **kwargs): super(ConditionalDescriptor, self).__init__(*args, **kwargs) - required_module_list = [tuple(x.split('/',1)) for x in self.metadata.get('required','').split('&')] + required_module_list = [tuple(x.split('/', 1)) for x in self.metadata.get('required', '').split('&')] self.required_module_locations = [] for (tag, name) in required_module_list: loc = self.location.dict() @@ -133,9 +135,8 @@ class ConditionalDescriptor(SequenceDescriptor): loc['name'] = name self.required_module_locations.append(Location(loc)) log.debug('ConditionalDescriptor required_module_locations=%s' % self.required_module_locations) - + def get_required_module_descriptors(self): """Returns a list of XModuleDescritpor instances upon which this module depends, but are not children of this module""" return [self.system.load_item(loc) for loc in self.required_module_locations] - diff --git a/common/lib/xmodule/xmodule/contentstore/content.py b/common/lib/xmodule/xmodule/contentstore/content.py index 5b10acc0ef..be33401bc8 100644 --- a/common/lib/xmodule/xmodule/contentstore/content.py +++ b/common/lib/xmodule/xmodule/contentstore/content.py @@ -11,15 +11,16 @@ from xmodule.modulestore import Location from .django import contentstore from PIL import Image + class StaticContent(object): def __init__(self, loc, name, content_type, data, last_modified_at=None, thumbnail_location=None, import_path=None): self.location = loc - self.name = name #a display string which can be edited, and thus not part of the location which needs to be fixed + self.name = name # a display string which can be edited, and thus not part of the location which needs to be fixed self.content_type = content_type self.data = data self.last_modified_at = last_modified_at self.thumbnail_location = Location(thumbnail_location) if thumbnail_location is not None else None - # optional information about where this file was imported from. This is needed to support import/export + # optional information about where this file was imported from. This is needed to support import/export # cycles self.import_path = import_path @@ -29,7 +30,7 @@ class StaticContent(object): @staticmethod def generate_thumbnail_name(original_name): - return ('{0}'+XASSET_THUMBNAIL_TAIL_NAME).format(os.path.splitext(original_name)[0]) + return ('{0}' + XASSET_THUMBNAIL_TAIL_NAME).format(os.path.splitext(original_name)[0]) @staticmethod def compute_location(org, course, name, revision=None, is_thumbnail=False): @@ -41,7 +42,7 @@ class StaticContent(object): def get_url_path(self): return StaticContent.get_url_path_from_location(self.location) - + @staticmethod def get_url_path_from_location(location): if location is not None: @@ -56,15 +57,15 @@ class StaticContent(object): @staticmethod def get_id_from_location(location): - return { 'tag':location.tag, 'org' : location.org, 'course' : location.course, - 'category' : location.category, 'name' : location.name, - 'revision' : location.revision} + return {'tag': location.tag, 'org': location.org, 'course': location.course, + 'category': location.category, 'name': location.name, + 'revision': location.revision} @staticmethod def get_location_from_path(path): # remove leading / character if it is there one if path.startswith('/'): path = path[1:] - + return Location(path.split('/')) @staticmethod @@ -77,7 +78,7 @@ class StaticContent(object): return StaticContent.get_url_path_from_location(loc) - + class ContentStore(object): ''' @@ -95,14 +96,14 @@ class ContentStore(object): [ - {u'displayname': u'profile.jpg', u'chunkSize': 262144, u'length': 85374, - u'uploadDate': datetime.datetime(2012, 10, 3, 5, 41, 54, 183000), u'contentType': u'image/jpeg', - u'_id': {u'category': u'asset', u'name': u'profile.jpg', u'course': u'6.002x', u'tag': u'c4x', - u'org': u'MITx', u'revision': None}, u'md5': u'36dc53519d4b735eb6beba51cd686a0e'}, + {u'displayname': u'profile.jpg', u'chunkSize': 262144, u'length': 85374, + u'uploadDate': datetime.datetime(2012, 10, 3, 5, 41, 54, 183000), u'contentType': u'image/jpeg', + u'_id': {u'category': u'asset', u'name': u'profile.jpg', u'course': u'6.002x', u'tag': u'c4x', + u'org': u'MITx', u'revision': None}, u'md5': u'36dc53519d4b735eb6beba51cd686a0e'}, - {u'displayname': u'profile.thumbnail.jpg', u'chunkSize': 262144, u'length': 4073, - u'uploadDate': datetime.datetime(2012, 10, 3, 5, 41, 54, 196000), u'contentType': u'image/jpeg', - u'_id': {u'category': u'asset', u'name': u'profile.thumbnail.jpg', u'course': u'6.002x', u'tag': u'c4x', + {u'displayname': u'profile.thumbnail.jpg', u'chunkSize': 262144, u'length': 4073, + u'uploadDate': datetime.datetime(2012, 10, 3, 5, 41, 54, 196000), u'contentType': u'image/jpeg', + u'_id': {u'category': u'asset', u'name': u'profile.thumbnail.jpg', u'course': u'6.002x', u'tag': u'c4x', u'org': u'MITx', u'revision': None}, u'md5': u'ff1532598830e3feac91c2449eaa60d6'}, .... @@ -117,7 +118,7 @@ class ContentStore(object): thumbnail_name = StaticContent.generate_thumbnail_name(content.location.name) thumbnail_file_location = StaticContent.compute_location(content.location.org, content.location.course, - thumbnail_name, is_thumbnail = True) + thumbnail_name, is_thumbnail=True) # if we're uploading an image, then let's generate a thumbnail so that we can # serve it up when needed without having to rescale on the fly @@ -129,7 +130,7 @@ class ContentStore(object): # @todo: move the thumbnail size to a configuration setting?!? im = Image.open(StringIO.StringIO(content.data)) - # I've seen some exceptions from the PIL library when trying to save palletted + # I've seen some exceptions from the PIL library when trying to save palletted # PNG files to JPEG. Per the google-universe, they suggest converting to RGB first. im = im.convert('RGB') size = 128, 128 @@ -139,7 +140,7 @@ class ContentStore(object): thumbnail_file.seek(0) # store this thumbnail as any other piece of content - thumbnail_content = StaticContent(thumbnail_file_location, thumbnail_name, + thumbnail_content = StaticContent(thumbnail_file_location, thumbnail_name, 'image/jpeg', thumbnail_file) contentstore().save(thumbnail_content) @@ -149,7 +150,3 @@ class ContentStore(object): logging.exception("Failed to generate thumbnail for {0}. Exception: {1}".format(content.location, str(e))) return thumbnail_content, thumbnail_file_location - - - - diff --git a/common/lib/xmodule/xmodule/contentstore/django.py b/common/lib/xmodule/xmodule/contentstore/django.py index d8b3084135..ec0397a348 100644 --- a/common/lib/xmodule/xmodule/contentstore/django.py +++ b/common/lib/xmodule/xmodule/contentstore/django.py @@ -6,6 +6,7 @@ from django.conf import settings _CONTENTSTORE = None + def load_function(path): """ Load a function by name. diff --git a/common/lib/xmodule/xmodule/contentstore/mongo.py b/common/lib/xmodule/xmodule/contentstore/mongo.py index 01f189a9e4..68cc6d73d3 100644 --- a/common/lib/xmodule/xmodule/contentstore/mongo.py +++ b/common/lib/xmodule/xmodule/contentstore/mongo.py @@ -17,14 +17,14 @@ import os class MongoContentStore(ContentStore): def __init__(self, host, db, port=27017, user=None, password=None, **kwargs): - logging.debug( 'Using MongoDB for static content serving at host={0} db={1}'.format(host,db)) + logging.debug('Using MongoDB for static content serving at host={0} db={1}'.format(host, db)) _db = Connection(host=host, port=port, **kwargs)[db] if user is not None and password is not None: _db.authenticate(user, password) self.fs = gridfs.GridFS(_db) - self.fs_files = _db["fs.files"] # the underlying collection GridFS uses + self.fs_files = _db["fs.files"] # the underlying collection GridFS uses def save(self, content): @@ -33,24 +33,24 @@ class MongoContentStore(ContentStore): # Seems like with the GridFS we can't update existing ID's we have to do a delete/add pair self.delete(id) - with self.fs.new_file(_id = id, filename=content.get_url_path(), content_type=content.content_type, + with self.fs.new_file(_id=id, filename=content.get_url_path(), content_type=content.content_type, displayname=content.name, thumbnail_location=content.thumbnail_location, import_path=content.import_path) as fp: fp.write(content.data) - + return content - + def delete(self, id): - if self.fs.exists({"_id" : id}): + if self.fs.exists({"_id": id}): self.fs.delete(id) def find(self, location): id = StaticContent.get_id_from_location(location) try: with self.fs.get(id) as fp: - return StaticContent(location, fp.displayname, fp.content_type, fp.read(), - fp.uploadDate, thumbnail_location = fp.thumbnail_location if hasattr(fp, 'thumbnail_location') else None, - import_path = fp.import_path if hasattr(fp, 'import_path') else None) + return StaticContent(location, fp.displayname, fp.content_type, fp.read(), + fp.uploadDate, thumbnail_location=fp.thumbnail_location if hasattr(fp, 'thumbnail_location') else None, + import_path=fp.import_path if hasattr(fp, 'import_path') else None) except NoFile: raise NotFoundError() @@ -76,25 +76,25 @@ class MongoContentStore(ContentStore): self.export(asset_location, output_directory) def get_all_content_thumbnails_for_course(self, location): - return self._get_all_content_for_course(location, get_thumbnails = True) + return self._get_all_content_for_course(location, get_thumbnails=True) def get_all_content_for_course(self, location): - return self._get_all_content_for_course(location, get_thumbnails = False) + return self._get_all_content_for_course(location, get_thumbnails=False) - def _get_all_content_for_course(self, location, get_thumbnails = False): + def _get_all_content_for_course(self, location, get_thumbnails=False): ''' Returns a list of all static assets for a course. The return format is a list of dictionary elements. Example: [ - {u'displayname': u'profile.jpg', u'chunkSize': 262144, u'length': 85374, - u'uploadDate': datetime.datetime(2012, 10, 3, 5, 41, 54, 183000), u'contentType': u'image/jpeg', - u'_id': {u'category': u'asset', u'name': u'profile.jpg', u'course': u'6.002x', u'tag': u'c4x', - u'org': u'MITx', u'revision': None}, u'md5': u'36dc53519d4b735eb6beba51cd686a0e'}, + {u'displayname': u'profile.jpg', u'chunkSize': 262144, u'length': 85374, + u'uploadDate': datetime.datetime(2012, 10, 3, 5, 41, 54, 183000), u'contentType': u'image/jpeg', + u'_id': {u'category': u'asset', u'name': u'profile.jpg', u'course': u'6.002x', u'tag': u'c4x', + u'org': u'MITx', u'revision': None}, u'md5': u'36dc53519d4b735eb6beba51cd686a0e'}, - {u'displayname': u'profile.thumbnail.jpg', u'chunkSize': 262144, u'length': 4073, - u'uploadDate': datetime.datetime(2012, 10, 3, 5, 41, 54, 196000), u'contentType': u'image/jpeg', - u'_id': {u'category': u'asset', u'name': u'profile.thumbnail.jpg', u'course': u'6.002x', u'tag': u'c4x', + {u'displayname': u'profile.thumbnail.jpg', u'chunkSize': 262144, u'length': 4073, + u'uploadDate': datetime.datetime(2012, 10, 3, 5, 41, 54, 196000), u'contentType': u'image/jpeg', + u'_id': {u'category': u'asset', u'name': u'profile.thumbnail.jpg', u'course': u'6.002x', u'tag': u'c4x', u'org': u'MITx', u'revision': None}, u'md5': u'ff1532598830e3feac91c2449eaa60d6'}, .... @@ -102,10 +102,7 @@ class MongoContentStore(ContentStore): ] ''' course_filter = Location(XASSET_LOCATION_TAG, category="asset" if not get_thumbnails else "thumbnail", - course=location.course,org=location.org) + course=location.course, org=location.org) # 'borrow' the function 'location_to_query' from the Mongo modulestore implementation items = self.fs_files.find(location_to_query(course_filter)) return list(items) - - - diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 6e3e2cfa39..750c8615a0 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -147,37 +147,37 @@ class CourseDescriptor(SequenceDescriptor): """ Return a dict which is a copy of the default grading policy """ - default = {"GRADER" : [ + default = {"GRADER": [ { - "type" : "Homework", - "min_count" : 12, - "drop_count" : 2, - "short_label" : "HW", - "weight" : 0.15 + "type": "Homework", + "min_count": 12, + "drop_count": 2, + "short_label": "HW", + "weight": 0.15 }, { - "type" : "Lab", - "min_count" : 12, - "drop_count" : 2, - "weight" : 0.15 + "type": "Lab", + "min_count": 12, + "drop_count": 2, + "weight": 0.15 }, { - "type" : "Midterm Exam", - "short_label" : "Midterm", - "min_count" : 1, - "drop_count" : 0, - "weight" : 0.3 + "type": "Midterm Exam", + "short_label": "Midterm", + "min_count": 1, + "drop_count": 0, + "weight": 0.3 }, { - "type" : "Final Exam", - "short_label" : "Final", - "min_count" : 1, - "drop_count" : 0, - "weight" : 0.4 + "type": "Final Exam", + "short_label": "Final", + "min_count": 1, + "drop_count": 0, + "weight": 0.4 } ], - "GRADE_CUTOFFS" : { - "Pass" : 0.5 + "GRADE_CUTOFFS": { + "Pass": 0.5 }} return copy.deepcopy(default) @@ -230,8 +230,8 @@ class CourseDescriptor(SequenceDescriptor): # bleh, have to parse the XML here to just pull out the url_name attribute # I don't think it's stored anywhere in the instance. - course_file = StringIO(xml_data.encode('ascii','ignore')) - xml_obj = etree.parse(course_file,parser=edx_xml_parser).getroot() + course_file = StringIO(xml_data.encode('ascii', 'ignore')) + xml_obj = etree.parse(course_file, parser=edx_xml_parser).getroot() policy_dir = None url_name = xml_obj.get('url_name', xml_obj.get('slug')) @@ -329,7 +329,7 @@ class CourseDescriptor(SequenceDescriptor): def raw_grader(self, value): # NOTE WELL: this change will not update the processed graders. If we need that, this needs to call grader_from_conf self._grading_policy['RAW_GRADER'] = value - self.definition['data'].setdefault('grading_policy',{})['GRADER'] = value + self.definition['data'].setdefault('grading_policy', {})['GRADER'] = value @property def grade_cutoffs(self): @@ -338,7 +338,7 @@ class CourseDescriptor(SequenceDescriptor): @grade_cutoffs.setter def grade_cutoffs(self, value): self._grading_policy['GRADE_CUTOFFS'] = value - self.definition['data'].setdefault('grading_policy',{})['GRADE_CUTOFFS'] = value + self.definition['data'].setdefault('grading_policy', {})['GRADE_CUTOFFS'] = value @property @@ -377,7 +377,7 @@ class CourseDescriptor(SequenceDescriptor): Return list of topic ids defined in course policy. """ topics = self.metadata.get("discussion_topics", {}) - return [d["id"] for d in topics.values()] + return [d["id"] for d in topics.values()] @property @@ -436,10 +436,10 @@ class CourseDescriptor(SequenceDescriptor): scale = 300.0 # about a year if announcement: days = (now - announcement).days - score = -exp(-days/scale) + score = -exp(-days / scale) else: days = (now - start).days - score = exp(days/scale) + score = exp(days / scale) return score def _sorting_dates(self): @@ -501,16 +501,16 @@ class CourseDescriptor(SequenceDescriptor): xmoduledescriptors.append(s) # The xmoduledescriptors included here are only the ones that have scores. - section_description = { 'section_descriptor' : s, 'xmoduledescriptors' : filter(lambda child: child.has_score, xmoduledescriptors) } + section_description = {'section_descriptor': s, 'xmoduledescriptors': filter(lambda child: child.has_score, xmoduledescriptors)} section_format = s.metadata.get('format', "") - graded_sections[ section_format ] = graded_sections.get( section_format, [] ) + [section_description] + graded_sections[section_format] = graded_sections.get(section_format, []) + [section_description] all_descriptors.extend(xmoduledescriptors) all_descriptors.append(s) - return { 'graded_sections' : graded_sections, - 'all_descriptors' : all_descriptors,} + return {'graded_sections': graded_sections, + 'all_descriptors': all_descriptors, } @staticmethod @@ -636,7 +636,7 @@ class CourseDescriptor(SequenceDescriptor): # *end* of the same day, not the same time. It's going to be used as the # end of the exam overall, so we don't want the exam to disappear too soon. # It's also used optionally as the registration end date, so time matters there too. - self.last_eligible_appointment_date = self._try_parse_time('Last_Eligible_Appointment_Date') # or self.first_eligible_appointment_date + self.last_eligible_appointment_date = self._try_parse_time('Last_Eligible_Appointment_Date') # or self.first_eligible_appointment_date if self.last_eligible_appointment_date is None: raise ValueError("Last appointment date must be specified") self.registration_start_date = self._try_parse_time('Registration_Start_Date') or time.gmtime(0) @@ -715,4 +715,3 @@ class CourseDescriptor(SequenceDescriptor): @property def org(self): return self.location.org - diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index 57d7780d95..6ddfcbe6c0 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -6,6 +6,7 @@ from xmodule.raw_module import RawDescriptor import json + class DiscussionModule(XModule): js = {'coffee': [resource_string(__name__, 'js/src/time.coffee'), @@ -30,6 +31,7 @@ class DiscussionModule(XModule): self.title = xml_data.attrib['for'] self.discussion_category = xml_data.attrib['discussion_category'] + class DiscussionDescriptor(RawDescriptor): module_class = DiscussionModule template_dir_name = "discussion" diff --git a/common/lib/xmodule/xmodule/errortracker.py b/common/lib/xmodule/xmodule/errortracker.py index 6accc8b8a7..80e6d288f8 100644 --- a/common/lib/xmodule/xmodule/errortracker.py +++ b/common/lib/xmodule/xmodule/errortracker.py @@ -8,12 +8,14 @@ log = logging.getLogger(__name__) ErrorLog = namedtuple('ErrorLog', 'tracker errors') + def exc_info_to_str(exc_info): """Given some exception info, convert it into a string using the traceback.format_exception() function. """ return ''.join(traceback.format_exception(*exc_info)) + def in_exception_handler(): '''Is there an active exception?''' return sys.exc_info() != (None, None, None) @@ -44,6 +46,7 @@ def make_error_tracker(): return ErrorLog(error_tracker, errors) + def null_error_tracker(msg): '''A dummy error tracker that just ignores the messages''' pass diff --git a/common/lib/xmodule/xmodule/graders.py b/common/lib/xmodule/xmodule/graders.py index 3e6d61eb00..35318f4f1e 100644 --- a/common/lib/xmodule/xmodule/graders.py +++ b/common/lib/xmodule/xmodule/graders.py @@ -49,6 +49,7 @@ def invalid_args(func, argdict): if keywords: return set() # All accepted return set(argdict) - set(args) + def grader_from_conf(conf): """ This creates a CourseGrader from a configuration (such as in course_settings.py). @@ -80,7 +81,7 @@ def grader_from_conf(conf): subgrader_class = SingleSectionGrader else: raise ValueError("Configuration has no appropriate grader class.") - + bad_args = invalid_args(subgrader_class.__init__, subgraderconf) # See note above concerning 'name'. if bad_args.issuperset({name}): @@ -90,7 +91,7 @@ def grader_from_conf(conf): log.warning("Invalid arguments for a subgrader: %s", bad_args) for key in bad_args: del subgraderconf[key] - + subgrader = subgrader_class(**subgraderconf) subgraders.append((subgrader, subgrader.category, weight)) @@ -210,13 +211,13 @@ class SingleSectionGrader(CourseGrader): break if foundScore or generate_random_scores: - if generate_random_scores: # for debugging! - earned = random.randint(2,15) + if generate_random_scores: # for debugging! + earned = random.randint(2, 15) possible = random.randint(earned, 15) - else: # We found the score + else: # We found the score earned = foundScore.earned possible = foundScore.possible - + percent = earned / float(possible) detail = "{name} - {percent:.0%} ({earned:.3n}/{possible:.3n})".format(name=self.name, percent=percent, @@ -245,7 +246,7 @@ class AssignmentFormatGrader(CourseGrader): min_count defines how many assignments are expected throughout the course. Placeholder scores (of 0) will be inserted if the number of matching sections in the course is < min_count. If there number of matching sections in the course is > min_count, min_count will be ignored. - + show_only_average is to suppress the display of each assignment in this grader and instead only show the total score of this grader in the breakdown. @@ -257,7 +258,7 @@ class AssignmentFormatGrader(CourseGrader): short_label is similar to section_type, but shorter. For example, for Homework it would be "HW". - + starting_index is the first number that will appear. For example, starting_index=3 and min_count = 2 would produce the labels "Assignment 3", "Assignment 4" @@ -296,16 +297,16 @@ class AssignmentFormatGrader(CourseGrader): breakdown = [] for i in range(max(self.min_count, len(scores))): if i < len(scores) or generate_random_scores: - if generate_random_scores: # for debugging! - earned = random.randint(2,15) - possible = random.randint(earned, 15) + if generate_random_scores: # for debugging! + earned = random.randint(2, 15) + possible = random.randint(earned, 15) section_name = "Generated" - + else: earned = scores[i].earned possible = scores[i].possible section_name = scores[i].section - + percentage = earned / float(possible) summary = "{section_type} {index} - {name} - {percent:.0%} ({earned:.3n}/{possible:.3n})".format(index=i + self.starting_index, section_type=self.section_type, @@ -318,7 +319,7 @@ class AssignmentFormatGrader(CourseGrader): summary = "{section_type} {index} Unreleased - 0% (?/?)".format(index=i + self.starting_index, section_type=self.section_type) short_label = "{short_label} {index:02d}".format(index=i + self.starting_index, short_label=self.short_label) - + breakdown.append({'percent': percentage, 'label': short_label, 'detail': summary, 'category': self.category}) total_percent, dropped_indices = totalWithDrops(breakdown, self.drop_count) @@ -328,13 +329,13 @@ class AssignmentFormatGrader(CourseGrader): total_detail = "{section_type} Average = {percent:.0%}".format(percent=total_percent, section_type=self.section_type) total_label = "{short_label} Avg".format(short_label=self.short_label) - + if self.show_only_average: breakdown = [] - + if not self.hide_average: breakdown.append({'percent': total_percent, 'label': total_label, 'detail': total_detail, 'category': self.category, 'prominent': True}) - + return {'percent': total_percent, 'section_breakdown': breakdown, #No grade_breakdown here diff --git a/common/lib/xmodule/xmodule/grading_service_module.py b/common/lib/xmodule/xmodule/grading_service_module.py index 7c18731f53..a442f39f34 100644 --- a/common/lib/xmodule/xmodule/grading_service_module.py +++ b/common/lib/xmodule/xmodule/grading_service_module.py @@ -10,9 +10,11 @@ from lxml import etree log = logging.getLogger(__name__) + class GradingServiceError(Exception): pass + class GradingService(object): """ Interface to staff grading backend. @@ -35,7 +37,7 @@ class GradingService(object): """ response = self.session.post(self.login_url, {'username': self.username, - 'password': self.password,}) + 'password': self.password, }) response.raise_for_status() @@ -124,4 +126,4 @@ class GradingService(object): except ValueError: log.exception("Error parsing response: {0}".format(response)) return {'success': False, - 'error': "Error displaying submission"} \ No newline at end of file + 'error': "Error displaying submission"} diff --git a/common/lib/xmodule/xmodule/html_checker.py b/common/lib/xmodule/xmodule/html_checker.py index 5e6b417d28..b30e5163a2 100644 --- a/common/lib/xmodule/xmodule/html_checker.py +++ b/common/lib/xmodule/xmodule/html_checker.py @@ -1,5 +1,6 @@ from lxml import etree + def check_html(html): ''' Check whether the passed in html string can be parsed by lxml. diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py index 612e78ce35..af1ce0ad80 100644 --- a/common/lib/xmodule/xmodule/html_module.py +++ b/common/lib/xmodule/xmodule/html_module.py @@ -133,7 +133,7 @@ class HtmlDescriptor(XmlDescriptor, EditingDescriptor): # TODO (ichuang): remove this after migration # for Fall 2012 LMS migration: keep filename (and unmangled filename) - definition['filename'] = [ filepath, filename ] + definition['filename'] = [filepath, filename] return definition @@ -180,6 +180,7 @@ class AboutDescriptor(HtmlDescriptor): """ template_dir_name = "about" + class StaticTabDescriptor(HtmlDescriptor): """ These pieces of course content are treated as HtmlModules but we need to overload where the templates are located @@ -187,6 +188,7 @@ class StaticTabDescriptor(HtmlDescriptor): """ template_dir_name = "statictab" + class CourseInfoDescriptor(HtmlDescriptor): """ These pieces of course content are treated as HtmlModules but we need to overload where the templates are located diff --git a/common/lib/xmodule/xmodule/mako_module.py b/common/lib/xmodule/xmodule/mako_module.py index f5f2fae23b..dab5d5e85b 100644 --- a/common/lib/xmodule/xmodule/mako_module.py +++ b/common/lib/xmodule/xmodule/mako_module.py @@ -34,7 +34,7 @@ class MakoModuleDescriptor(XModuleDescriptor): """ return {'module': self, 'metadata': self.metadata, - 'editable_metadata_fields' : self.editable_metadata_fields + 'editable_metadata_fields': self.editable_metadata_fields } def get_html(self): @@ -46,4 +46,3 @@ class MakoModuleDescriptor(XModuleDescriptor): def editable_metadata_fields(self): subset = [name for name in self.metadata.keys() if name not in self.system_metadata_fields] return subset - diff --git a/common/lib/xmodule/xmodule/modulestore/__init__.py b/common/lib/xmodule/xmodule/modulestore/__init__.py index 38915adcb4..a9df6c3504 100644 --- a/common/lib/xmodule/xmodule/modulestore/__init__.py +++ b/common/lib/xmodule/xmodule/modulestore/__init__.py @@ -365,7 +365,7 @@ class ModuleStore(object): raise NotImplementedError def get_parent_locations(self, location, course_id): - '''Find all locations that are the parents of this location in this + '''Find all locations that are the parents of this location in this course. Needed for path_to_location(). returns an iterable of things that can be passed to Location. diff --git a/common/lib/xmodule/xmodule/modulestore/draft.py b/common/lib/xmodule/xmodule/modulestore/draft.py index ef2a848cac..81f4da2780 100644 --- a/common/lib/xmodule/xmodule/modulestore/draft.py +++ b/common/lib/xmodule/xmodule/modulestore/draft.py @@ -67,7 +67,7 @@ class DraftModuleStore(ModuleStoreBase): TODO (vshnayder): this may want to live outside the modulestore eventually """ - # cdodge: we're forcing depth=0 here as the Draft store is not handling caching well + # cdodge: we're forcing depth=0 here as the Draft store is not handling caching well try: return wrap_draft(super(DraftModuleStore, self).get_instance(course_id, as_draft(location), depth=0)) except ItemNotFoundError: diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py index 3b92876673..f4db62ac31 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo.py @@ -304,7 +304,7 @@ class MongoModuleStore(ModuleStoreBase): if location.category == 'static_tab': course = self.get_course_for_item(item.location) existing_tabs = course.tabs or [] - existing_tabs.append({'type':'static_tab', 'name' : item.metadata.get('display_name'), 'url_slug' : item.location.name}) + existing_tabs.append({'type': 'static_tab', 'name': item.metadata.get('display_name'), 'url_slug': item.location.name}) course.tabs = existing_tabs self.update_metadata(course.location, course.metadata) @@ -423,7 +423,7 @@ class MongoModuleStore(ModuleStoreBase): def get_parent_locations(self, location, course_id): - '''Find all locations that are the parents of this location in this + '''Find all locations that are the parents of this location in this course. Needed for path_to_location(). ''' location = Location.ensure_fully_specified(location) diff --git a/common/lib/xmodule/xmodule/modulestore/search.py b/common/lib/xmodule/xmodule/modulestore/search.py index e53df84bb6..b56b612592 100644 --- a/common/lib/xmodule/xmodule/modulestore/search.py +++ b/common/lib/xmodule/xmodule/modulestore/search.py @@ -104,14 +104,14 @@ def path_to_location(modulestore, course_id, location): # module nested in more than one positional module will work. if n > 3: position_list = [] - for path_index in range(2, n-1): + for path_index in range(2, n - 1): category = path[path_index].category if category == 'sequential' or category == 'videosequence': section_desc = modulestore.get_instance(course_id, path[path_index]) child_locs = [c.location for c in section_desc.get_children()] # positions are 1-indexed, and should be strings to be consistent with # url parsing. - position_list.append(str(child_locs.index(path[path_index+1]) + 1)) + position_list.append(str(child_locs.index(path[path_index + 1]) + 1)) position = "_".join(position_list) return (course_id, chapter, section, position) diff --git a/common/lib/xmodule/xmodule/modulestore/store_utilities.py b/common/lib/xmodule/xmodule/modulestore/store_utilities.py index af346dbb7e..192b012bef 100644 --- a/common/lib/xmodule/xmodule/modulestore/store_utilities.py +++ b/common/lib/xmodule/xmodule/modulestore/store_utilities.py @@ -3,6 +3,7 @@ from xmodule.contentstore.content import StaticContent from xmodule.modulestore import Location from xmodule.modulestore.mongo import MongoModuleStore + def clone_course(modulestore, contentstore, source_location, dest_location, delete_original=False): # first check to see if the modulestore is Mongo backed if not isinstance(modulestore, MongoModuleStore): @@ -13,7 +14,7 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele if not modulestore.has_item(dest_location): raise Exception("An empty course at {0} must have already been created. Aborting...".format(dest_location)) - # verify that the dest_location really is an empty course, which means only one + # verify that the dest_location really is an empty course, which means only one dest_modules = modulestore.get_items([dest_location.tag, dest_location.org, dest_location.course, None, None, None]) if len(dest_modules) != 1: @@ -31,12 +32,12 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele original_loc = Location(module.location) if original_loc.category != 'course': - module.location = module.location._replace(tag = dest_location.tag, org = dest_location.org, - course = dest_location.course) + module.location = module.location._replace(tag=dest_location.tag, org=dest_location.org, + course=dest_location.course) else: # on the course module we also have to update the module name - module.location = module.location._replace(tag = dest_location.tag, org = dest_location.org, - course = dest_location.course, name=dest_location.name) + module.location = module.location._replace(tag=dest_location.tag, org=dest_location.org, + course=dest_location.course, name=dest_location.name) print "Cloning module {0} to {1}....".format(original_loc, module.location) @@ -48,8 +49,8 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele new_children = [] for child_loc_url in module.definition['children']: child_loc = Location(child_loc_url) - child_loc = child_loc._replace(tag = dest_location.tag, org = dest_location.org, - course = dest_location.course) + child_loc = child_loc._replace(tag=dest_location.tag, org=dest_location.org, + course=dest_location.course) new_children = new_children + [child_loc.url()] modulestore.update_children(module.location, new_children) @@ -63,8 +64,8 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele for thumb in thumbs: thumb_loc = Location(thumb["_id"]) content = contentstore.find(thumb_loc) - content.location = content.location._replace(org = dest_location.org, - course = dest_location.course) + content.location = content.location._replace(org=dest_location.org, + course=dest_location.course) print "Cloning thumbnail {0} to {1}".format(thumb_loc, content.location) @@ -76,13 +77,13 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele for asset in assets: asset_loc = Location(asset["_id"]) content = contentstore.find(asset_loc) - content.location = content.location._replace(org = dest_location.org, - course = dest_location.course) + content.location = content.location._replace(org=dest_location.org, + course=dest_location.course) # be sure to update the pointer to the thumbnail if content.thumbnail_location is not None: - content.thumbnail_location = content.thumbnail_location._replace(org = dest_location.org, - course = dest_location.course) + content.thumbnail_location = content.thumbnail_location._replace(org=dest_location.org, + course=dest_location.course) print "Cloning asset {0} to {1}".format(asset_loc, content.location) @@ -90,6 +91,7 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele return True + def delete_course(modulestore, contentstore, source_location): # first check to see if the modulestore is Mongo backed if not isinstance(modulestore, MongoModuleStore): @@ -119,7 +121,7 @@ def delete_course(modulestore, contentstore, source_location): modules = modulestore.get_items([source_location.tag, source_location.org, source_location.course, None, None, None]) for module in modules: - if module.category != 'course': # save deleting the course module for last + if module.category != 'course': # save deleting the course module for last print "Deleting {0}...".format(module.location) modulestore.delete_item(module.location) @@ -127,4 +129,4 @@ def delete_course(modulestore, contentstore, source_location): print "Deleting {0}...".format(source_location) modulestore.delete_item(source_location) - return True \ No newline at end of file + return True diff --git a/common/lib/xmodule/xmodule/modulestore/tests/__init__.py b/common/lib/xmodule/xmodule/modulestore/tests/__init__.py index 126f0136e2..2759f2540c 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/__init__.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/__init__.py @@ -8,5 +8,3 @@ for i in range(5): TEST_DIR = TEST_DIR / 'test' DATA_DIR = TEST_DIR / 'data' - - diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index b4264b30c9..1259da2690 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -5,12 +5,15 @@ from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore from xmodule.timeparse import stringify_time -def XMODULE_COURSE_CREATION(class_to_create, **kwargs): + +def XMODULE_COURSE_CREATION(class_to_create, **kwargs): return XModuleCourseFactory._create(class_to_create, **kwargs) + def XMODULE_ITEM_CREATION(class_to_create, **kwargs): return XModuleItemFactory._create(class_to_create, **kwargs) + class XModuleCourseFactory(Factory): """ Factory for XModule courses. @@ -27,7 +30,7 @@ class XModuleCourseFactory(Factory): org = kwargs.get('org') number = kwargs.get('number') display_name = kwargs.get('display_name') - location = Location('i4x', org, number, + location = Location('i4x', org, number, 'course', Location.clean(display_name)) store = modulestore('direct') @@ -42,20 +45,22 @@ class XModuleCourseFactory(Factory): new_course.metadata['data_dir'] = uuid4().hex new_course.metadata['start'] = stringify_time(gmtime()) - new_course.tabs = [{"type": "courseware"}, + new_course.tabs = [{"type": "courseware"}, {"type": "course_info", "name": "Course Info"}, {"type": "discussion", "name": "Discussion"}, {"type": "wiki", "name": "Wiki"}, {"type": "progress", "name": "Progress"}] # Update the data in the mongo datastore - store.update_metadata(new_course.location.url(), new_course.own_metadata) + store.update_metadata(new_course.location.url(), new_course.own_metadata) return new_course + class Course: pass + class CourseFactory(XModuleCourseFactory): FACTORY_FOR = Course @@ -64,6 +69,7 @@ class CourseFactory(XModuleCourseFactory): number = '999' display_name = 'Robot Super Course' + class XModuleItemFactory(Factory): """ Factory for XModule items. @@ -80,7 +86,7 @@ class XModuleItemFactory(Factory): """ DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info'] - + parent_location = Location(kwargs.get('parent_location')) template = Location(kwargs.get('template')) display_name = kwargs.get('display_name') @@ -107,9 +113,11 @@ class XModuleItemFactory(Factory): return new_item + class Item: pass + class ItemFactory(XModuleItemFactory): FACTORY_FOR = Item diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_location.py b/common/lib/xmodule/xmodule/modulestore/tests/test_location.py index afe5e47d10..0772951884 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_location.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_location.py @@ -61,6 +61,7 @@ invalid = ("foo", ["foo"], ["foo", "bar"], invalid_dict, invalid_dict2) + def test_is_valid(): for v in valid: assert_equals(Location.is_valid(v), True) @@ -68,6 +69,7 @@ def test_is_valid(): for v in invalid: assert_equals(Location.is_valid(v), False) + def test_dict(): assert_equals("tag://org/course/category/name", Location(input_dict).url()) assert_equals(dict(revision=None, **input_dict), Location(input_dict).dict()) @@ -76,6 +78,7 @@ def test_dict(): assert_equals("tag://org/course/category/name@revision", Location(input_dict).url()) assert_equals(input_dict, Location(input_dict).dict()) + def test_list(): assert_equals("tag://org/course/category/name", Location(input_list).url()) assert_equals(input_list + [None], Location(input_list).list()) @@ -115,17 +118,18 @@ def test_equality(): ) # All the cleaning functions should do the same thing with these -general_pairs = [ ('',''), +general_pairs = [('', ''), (' ', '_'), ('abc,', 'abc_'), ('ab fg!@//\\aj', 'ab_fg_aj'), (u"ab\xA9", "ab_"), # no unicode allowed for now ] + def test_clean(): pairs = general_pairs + [ ('a:b', 'a_b'), # no colons in non-name components - ('a-b', 'a-b'), # dashes ok + ('a-b', 'a-b'), # dashes ok ('a.b', 'a.b'), # dot ok ] for input, output in pairs: diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_modulestore.py b/common/lib/xmodule/xmodule/modulestore/tests/test_modulestore.py index 64816581ce..94ea622907 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_modulestore.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_modulestore.py @@ -3,6 +3,7 @@ from nose.tools import assert_equals, assert_raises, assert_not_equals, with_set from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem from xmodule.modulestore.search import path_to_location + def check_path_to_location(modulestore): '''Make sure that path_to_location works: should be passed a modulestore with the toy and simple courses loaded.''' @@ -22,4 +23,3 @@ def check_path_to_location(modulestore): ) for location in not_found: assert_raises(ItemNotFoundError, path_to_location, modulestore, course_id, location) - diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py index 4c593e391e..6f6f47ba85 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py @@ -102,4 +102,3 @@ class TestMongoModuleStore(object): def test_path_to_location(self): '''Make sure that path_to_location works''' check_path_to_location(self.store) - diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_xml.py b/common/lib/xmodule/xmodule/modulestore/tests/test_xml.py index c4446bebb5..321d98967b 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_xml.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_xml.py @@ -5,6 +5,7 @@ from xmodule.modulestore.xml_importer import import_from_xml from .test_modulestore import check_path_to_location from . import DATA_DIR + class TestXMLModuleStore(object): def test_path_to_location(self): """Make sure that path_to_location works properly""" @@ -12,5 +13,5 @@ class TestXMLModuleStore(object): print "Starting import" modulestore = XMLModuleStore(DATA_DIR, course_dirs=['toy', 'simple']) print "finished import" - + check_path_to_location(modulestore) diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py index 17d6f04932..8446162f26 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml.py +++ b/common/lib/xmodule/xmodule/modulestore/xml.py @@ -363,7 +363,7 @@ class XMLModuleStore(ModuleStoreBase): # been imported into the cms from xml course_file = StringIO(clean_out_mako_templating(course_file.read())) - course_data = etree.parse(course_file,parser=edx_xml_parser).getroot() + course_data = etree.parse(course_file, parser=edx_xml_parser).getroot() org = course_data.get('org') @@ -437,7 +437,7 @@ class XMLModuleStore(ModuleStoreBase): self.load_extra_content(system, course_descriptor, 'course_info', self.data_dir / course_dir / 'info', course_dir, url_name) # now import all static tabs which are expected to be stored in - # in /tabs or /tabs/ + # in /tabs or /tabs/ self.load_extra_content(system, course_descriptor, 'static_tab', self.data_dir / course_dir / 'tabs', course_dir, url_name) self.load_extra_content(system, course_descriptor, 'custom_tag_template', self.data_dir / course_dir / 'custom_tags', course_dir, url_name) @@ -454,12 +454,12 @@ class XMLModuleStore(ModuleStoreBase): # then look in a override folder based on the course run if os.path.isdir(base_dir / url_name): - self._load_extra_content(system, course_descriptor, category, base_dir / url_name, course_dir) + self._load_extra_content(system, course_descriptor, category, base_dir / url_name, course_dir) def _load_extra_content(self, system, course_descriptor, category, path, course_dir): - for filepath in glob.glob(path/ '*'): + for filepath in glob.glob(path / '*'): if not os.path.isdir(filepath): with open(filepath) as f: try: @@ -467,7 +467,7 @@ class XMLModuleStore(ModuleStoreBase): # tabs are referenced in policy.json through a 'slug' which is just the filename without the .html suffix slug = os.path.splitext(os.path.basename(filepath))[0] loc = Location('i4x', course_descriptor.location.org, course_descriptor.location.course, category, slug) - module = HtmlDescriptor(system, definition={'data' : html}, **{'location' : loc}) + module = HtmlDescriptor(system, definition={'data': html}, **{'location': loc}) # VS[compat]: # Hack because we need to pull in the 'display_name' for static tabs (because we need to edit them) # from the course policy @@ -555,7 +555,7 @@ class XMLModuleStore(ModuleStoreBase): Return a dictionary of course_dir -> [(msg, exception_str)], for each course_dir where course loading failed. """ - return dict( (k, self.errored_courses[k].errors) for k in self.errored_courses) + return dict((k, self.errored_courses[k].errors) for k in self.errored_courses) def update_item(self, location, data): """ @@ -590,7 +590,7 @@ class XMLModuleStore(ModuleStoreBase): raise NotImplementedError("XMLModuleStores are read-only") def get_parent_locations(self, location, course_id): - '''Find all locations that are the parents of this location in this + '''Find all locations that are the parents of this location in this course. Needed for path_to_location(). returns an iterable of things that can be passed to Location. This may diff --git a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py index 3522b45718..bdbd5a6133 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py @@ -3,6 +3,7 @@ from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore from fs.osfs import OSFS + def export_to_xml(modulestore, contentstore, course_location, root_dir, course_dir): course = modulestore.get_item(course_location) @@ -27,7 +28,7 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d export_extra_content(export_fs, modulestore, course_location, 'course_info', 'info', '.html') -def export_extra_content(export_fs, modulestore, course_location, category_type, dirname, file_suffix = ''): +def export_extra_content(export_fs, modulestore, course_location, category_type, dirname, file_suffix=''): query_loc = Location('i4x', course_location.org, course_location.course, category_type, None) items = modulestore.get_items(query_loc) @@ -36,7 +37,3 @@ def export_extra_content(export_fs, modulestore, course_location, category_type, for item in items: with item_dir.open(item.location.name + file_suffix, 'w') as item_file: item_file.write(item.definition['data'].encode('utf8')) - - - - \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index 7658d699d4..0b77900ae9 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py @@ -11,9 +11,10 @@ from xmodule.contentstore.content import StaticContent, XASSET_SRCREF_PREFIX log = logging.getLogger(__name__) -def import_static_content(modules, course_loc, course_data_path, static_content_store, target_location_namespace, - subpath = 'static', verbose=False): - + +def import_static_content(modules, course_loc, course_data_path, static_content_store, target_location_namespace, + subpath='static', verbose=False): + remap_dict = {} # now import all static assets @@ -36,7 +37,7 @@ def import_static_content(modules, course_loc, course_data_path, static_content_ with open(content_path, 'rb') as f: data = f.read() - content = StaticContent(content_loc, filename, mime_type, data, import_path = fullname_with_subpath) + content = StaticContent(content_loc, filename, mime_type, data, import_path=fullname_with_subpath) # first let's save a thumbnail so we can get back a thumbnail location (thumbnail_content, thumbnail_location) = static_content_store.generate_thumbnail(content) @@ -50,11 +51,12 @@ def import_static_content(modules, course_loc, course_data_path, static_content_ #store the remapping information which will be needed to subsitute in the module data remap_dict[fullname_with_subpath] = content_loc.name except: - raise + raise return remap_dict -def verify_content_links(module, base_dir, static_content_store, link, remap_dict = None): + +def verify_content_links(module, base_dir, static_content_store, link, remap_dict=None): if link.startswith('/static/'): # yes, then parse out the name path = link[len('/static/'):] @@ -70,7 +72,7 @@ def verify_content_links(module, base_dir, static_content_store, link, remap_dic with open(static_pathname, 'rb') as f: data = f.read() - content = StaticContent(content_loc, filename, mime_type, data, import_path = path) + content = StaticContent(content_loc, filename, mime_type, data, import_path=path) # first let's save a thumbnail so we can get back a thumbnail location (thumbnail_content, thumbnail_location) = static_content_store.generate_thumbnail(content) @@ -79,20 +81,21 @@ def verify_content_links(module, base_dir, static_content_store, link, remap_dic content.thumbnail_location = thumbnail_location #then commit the content - static_content_store.save(content) + static_content_store.save(content) - new_link = StaticContent.get_url_path_from_location(content_loc) + new_link = StaticContent.get_url_path_from_location(content_loc) if remap_dict is not None: remap_dict[link] = new_link - return new_link + return new_link except Exception, e: logging.exception('Skipping failed content load from {0}. Exception: {1}'.format(path, e)) return link -def import_from_xml(store, data_dir, course_dirs=None, + +def import_from_xml(store, data_dir, course_dirs=None, default_class='xmodule.raw_module.RawDescriptor', load_error_modules=True, static_content_store=None, target_location_namespace=None, verbose=False): """ @@ -108,7 +111,7 @@ def import_from_xml(store, data_dir, course_dirs=None, the policy.json. so we need to keep the original url_name during import """ - + module_store = XMLModuleStore( data_dir, default_class=default_class, @@ -137,12 +140,12 @@ def import_from_xml(store, data_dir, course_dirs=None, module = remap_namespace(module, target_location_namespace) - # cdodge: more hacks (what else). Seems like we have a problem when importing a course (like 6.002) which - # does not have any tabs defined in the policy file. The import goes fine and then displays fine in LMS, - # but if someone tries to add a new tab in the CMS, then the LMS barfs because it expects that - + # cdodge: more hacks (what else). Seems like we have a problem when importing a course (like 6.002) which + # does not have any tabs defined in the policy file. The import goes fine and then displays fine in LMS, + # but if someone tries to add a new tab in the CMS, then the LMS barfs because it expects that - # if there is *any* tabs - then there at least needs to be some predefined ones if module.tabs is None or len(module.tabs) == 0: - module.tabs = [{"type": "courseware"}, + module.tabs = [{"type": "courseware"}, {"type": "course_info", "name": "Course Info"}, {"type": "discussion", "name": "Discussion"}, {"type": "wiki", "name": "Wiki"}] # note, add 'progress' when we can support it on Edge @@ -159,13 +162,13 @@ def import_from_xml(store, data_dir, course_dirs=None, course_items.append(module) - + # then import all the static content if static_content_store is not None: _namespace_rename = target_location_namespace if target_location_namespace is not None else course_location - + # first pass to find everything in /static/ - import_static_content(module_store.modules[course_id], course_location, course_data_path, static_content_store, + import_static_content(module_store.modules[course_id], course_location, course_data_path, static_content_store, _namespace_rename, subpath='static', verbose=verbose) # finally loop through all the modules @@ -188,18 +191,18 @@ def import_from_xml(store, data_dir, course_dirs=None, # cdodge: now go through any link references to '/static/' and make sure we've imported # it as a StaticContent asset - try: + try: remap_dict = {} # use the rewrite_links as a utility means to enumerate through all links # in the module data. We use that to load that reference into our asset store # IMPORTANT: There appears to be a bug in lxml.rewrite_link which makes us not be able to # do the rewrites natively in that code. - # For example, what I'm seeing is -> + # For example, what I'm seeing is -> # Note the dropped element closing tag. This causes the LMS to fail when rendering modules - that's # no good, so we have to do this kludge if isinstance(module_data, str) or isinstance(module_data, unicode): # some module 'data' fields are non strings which blows up the link traversal code - lxml_rewrite_links(module_data, lambda link: verify_content_links(module, course_data_path, + lxml_rewrite_links(module_data, lambda link: verify_content_links(module, course_data_path, static_content_store, link, remap_dict)) for key in remap_dict.keys(): @@ -219,17 +222,18 @@ def import_from_xml(store, data_dir, course_dirs=None, return module_store, course_items + def remap_namespace(module, target_location_namespace): if target_location_namespace is None: return module - + # This looks a bit wonky as we need to also change the 'name' of the imported course to be what # the caller passed in if module.location.category != 'course': - module.location = module.location._replace(tag=target_location_namespace.tag, org=target_location_namespace.org, + module.location = module.location._replace(tag=target_location_namespace.tag, org=target_location_namespace.org, course=target_location_namespace.course) else: - module.location = module.location._replace(tag=target_location_namespace.tag, org=target_location_namespace.org, + module.location = module.location._replace(tag=target_location_namespace.tag, org=target_location_namespace.org, course=target_location_namespace.course, name=target_location_namespace.name) # then remap children pointers since they too will be re-namespaced @@ -238,15 +242,16 @@ def remap_namespace(module, target_location_namespace): new_locs = [] for child in children_locs: child_loc = Location(child) - new_child_loc = child_loc._replace(tag=target_location_namespace.tag, org=target_location_namespace.org, + new_child_loc = child_loc._replace(tag=target_location_namespace.tag, org=target_location_namespace.org, course=target_location_namespace.course) new_locs.append(new_child_loc.url()) - module.definition['children'] = new_locs + module.definition['children'] = new_locs return module + def validate_category_hierarchy(module_store, course_id, parent_category, expected_child_category): err_cnt = 0 @@ -265,7 +270,8 @@ def validate_category_hierarchy(module_store, course_id, parent_category, expect return err_cnt -def validate_data_source_path_existence(path, is_err = True, extra_msg = None): + +def validate_data_source_path_existence(path, is_err=True, extra_msg=None): _cnt = 0 if not os.path.exists(path): print ("{0}: Expected folder at {1}. {2}".format('ERROR' if is_err == True else 'WARNING', path, extra_msg if @@ -273,18 +279,19 @@ def validate_data_source_path_existence(path, is_err = True, extra_msg = None): _cnt = 1 return _cnt + def validate_data_source_paths(data_dir, course_dir): # check that there is a '/static/' directory course_path = data_dir / course_dir err_cnt = 0 warn_cnt = 0 err_cnt += validate_data_source_path_existence(course_path / 'static') - warn_cnt += validate_data_source_path_existence(course_path / 'static/subs', is_err = False, - extra_msg = 'Video captions (if they are used) will not work unless they are static/subs.') + warn_cnt += validate_data_source_path_existence(course_path / 'static/subs', is_err=False, + extra_msg='Video captions (if they are used) will not work unless they are static/subs.') return err_cnt, warn_cnt -def perform_xlint(data_dir, course_dirs, +def perform_xlint(data_dir, course_dirs, default_class='xmodule.raw_module.RawDescriptor', load_error_modules=True): err_cnt = 0 @@ -308,9 +315,9 @@ def perform_xlint(data_dir, course_dirs, for err_log_entry in err_log.errors: msg = err_log_entry[0] if msg.startswith('ERROR:'): - err_cnt+=1 + err_cnt += 1 else: - warn_cnt+=1 + warn_cnt += 1 # then count outright all courses that failed to load at all for err_log in module_store.errored_courses.itervalues(): @@ -318,9 +325,9 @@ def perform_xlint(data_dir, course_dirs, msg = err_log_entry[0] print msg if msg.startswith('ERROR:'): - err_cnt+=1 + err_cnt += 1 else: - warn_cnt+=1 + warn_cnt += 1 for course_id in module_store.modules.keys(): # constrain that courses only have 'chapter' children @@ -345,6 +352,3 @@ def perform_xlint(data_dir, course_dirs, print "This course can be imported, but some errors may occur during the run of the course. It is recommend that you fix your courseware before importing" else: print "This course can be imported successfully." - - - diff --git a/common/lib/xmodule/xmodule/open_ended_image_submission.py b/common/lib/xmodule/xmodule/open_ended_image_submission.py index 8fa4d721d3..10b1a20a7c 100644 --- a/common/lib/xmodule/xmodule/open_ended_image_submission.py +++ b/common/lib/xmodule/xmodule/open_ended_image_submission.py @@ -51,6 +51,7 @@ MAX_COLORS_TO_COUNT = 16 #Maximum number of colors allowed in an uploaded image MAX_COLORS = 400 + class ImageProperties(object): """ Class to check properties of an image and to validate if they are allowed. @@ -187,6 +188,7 @@ class URLProperties(object): return success return success + def run_url_tests(url_string): """ Creates a URLProperties object and runs all tests @@ -244,7 +246,7 @@ def upload_to_s3(file_to_upload, keyname): #k.set_metadata("Content-Type", 'images/png') k.set_acl("public-read") - public_url = k.generate_url(60 * 60 * 24 * 365) # URL timeout in seconds. + public_url = k.generate_url(60 * 60 * 24 * 365) # URL timeout in seconds. return True, public_url except: @@ -260,6 +262,3 @@ def get_from_s3(s3_public_url): r = requests.get(s3_public_url, timeout=2) data = r.text return data - - - diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 94d45d96e3..072a7153fb 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -38,6 +38,7 @@ from combined_open_ended_rubric import CombinedOpenEndedRubric log = logging.getLogger("mitx.courseware") + class OpenEndedModule(openendedchild.OpenEndedChild): """ The open ended module supports all external open ended grader problems. @@ -300,7 +301,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): # We want to display available feedback in a particular order. # This dictionary specifies which goes first--lower first. - priorities = {# These go at the start of the feedback + priorities = { # These go at the start of the feedback 'spelling': 0, 'grammar': 1, # needs to be after all the other feedback @@ -674,5 +675,3 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): add_child(child) return elt - - diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index 7151ac0723..a56937270b 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -38,6 +38,7 @@ MAX_ATTEMPTS = 1 # Overriden by max_score specified in xml. MAX_SCORE = 1 + class OpenEndedChild(object): """ States: @@ -375,18 +376,13 @@ class OpenEndedChild(object): """ success = False links = re.findall(r'(https?://\S+)', string) - if len(links)>0: + if len(links) > 0: for link in links: success = open_ended_image_submission.run_url_tests(link) if not success: string = re.sub(link, '', string) else: - string = re.sub(link, self.generate_image_tag_from_url(link,link), string) + string = re.sub(link, self.generate_image_tag_from_url(link, link), string) success = True return success, string - - - - - diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index e853160f4a..20f71f3b3c 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -43,6 +43,7 @@ TRUE_DICT = [True, "True", "true", "TRUE"] MAX_SCORE = 1 IS_GRADED = True + class PeerGradingModule(XModule): _VERSION = 1 @@ -80,7 +81,7 @@ class PeerGradingModule(XModule): self.is_graded = (self.is_graded in TRUE_DICT) self.link_to_location = self.metadata.get('link_to_location', USE_FOR_SINGLE_LOCATION) - if self.use_for_single_location ==True: + if self.use_for_single_location == True: #This will raise an exception if the location is invalid link_to_location_object = Location(self.link_to_location) @@ -116,7 +117,7 @@ class PeerGradingModule(XModule): if not self.use_for_single_location: return self.peer_grading() else: - return self.peer_grading_problem({'location' : self.link_to_location})['html'] + return self.peer_grading_problem({'location': self.link_to_location})['html'] def handle_ajax(self, dispatch, get): """ @@ -128,8 +129,8 @@ class PeerGradingModule(XModule): 'show_calibration_essay': self.show_calibration_essay, 'is_student_calibrated': self.is_student_calibrated, 'save_grade': self.save_grade, - 'save_calibration_essay' : self.save_calibration_essay, - 'problem' : self.peer_grading_problem, + 'save_calibration_essay': self.save_calibration_essay, + 'problem': self.peer_grading_problem, } if dispatch not in handlers: @@ -175,11 +176,11 @@ class PeerGradingModule(XModule): return None count_graded = response['count_graded'] count_required = response['count_required'] - if count_required>0 and count_graded>=count_required: + if count_required > 0 and count_graded >= count_required: self.student_data_for_location = response score_dict = { - 'score': int(count_graded>=count_required), + 'score': int(count_graded >= count_required), 'total': self.max_grade, } @@ -399,7 +400,7 @@ class PeerGradingModule(XModule): log.exception("Error saving calibration grade, location: {0}, submission_id: {1}, submission_key: {2}, grader_id: {3}".format(location, submission_id, submission_key, grader_id)) return self._err_response('Could not connect to grading service') - def peer_grading(self, get = None): + def peer_grading(self, get=None): ''' Show a peer grading interface ''' @@ -434,19 +435,19 @@ class PeerGradingModule(XModule): 'error_text': error_text, # Checked above 'staff_access': False, - 'use_single_location' : self.use_for_single_location, + 'use_single_location': self.use_for_single_location, }) return html - def peer_grading_problem(self, get = None): + def peer_grading_problem(self, get=None): ''' Show individual problem interface ''' - if get == None or get.get('location')==None: + if get == None or get.get('location') == None: if not self.use_for_single_location: #This is an error case, because it must be set to use a single location to be called without get parameters - return {'html' : "", 'success' : False} + return {'html': "", 'success': False} problem_location = self.link_to_location elif get.get('location') is not None: @@ -460,10 +461,10 @@ class PeerGradingModule(XModule): 'ajax_url': ajax_url, # Checked above 'staff_access': False, - 'use_single_location' : self.use_for_single_location, + 'use_single_location': self.use_for_single_location, }) - return {'html' : html, 'success' : True} + return {'html': html, 'success': True} def get_instance_state(self): """ @@ -473,11 +474,12 @@ class PeerGradingModule(XModule): """ state = { - 'student_data_for_location' : self.student_data_for_location, + 'student_data_for_location': self.student_data_for_location, } return json.dumps(state) + class PeerGradingDescriptor(XmlDescriptor, EditingDescriptor): """ Module for adding combined open ended questions @@ -534,4 +536,4 @@ class PeerGradingDescriptor(XmlDescriptor, EditingDescriptor): for child in ['task']: add_child(child) - return elt \ No newline at end of file + return elt diff --git a/common/lib/xmodule/xmodule/peer_grading_service.py b/common/lib/xmodule/xmodule/peer_grading_service.py index 6b30f4e043..8c50b6ff0a 100644 --- a/common/lib/xmodule/xmodule/peer_grading_service.py +++ b/common/lib/xmodule/xmodule/peer_grading_service.py @@ -14,11 +14,13 @@ from combined_open_ended_rubric import CombinedOpenEndedRubric, RubricParsingErr from lxml import etree from grading_service_module import GradingService, GradingServiceError -log=logging.getLogger(__name__) +log = logging.getLogger(__name__) + class GradingServiceError(Exception): pass + class PeerGradingService(GradingService): """ Interface with the grading controller for peer grading @@ -47,23 +49,23 @@ class PeerGradingService(GradingService): return self.try_to_decode(self._render_rubric(response)) def save_grade(self, location, grader_id, submission_id, score, feedback, submission_key, rubric_scores, submission_flagged): - data = {'grader_id' : grader_id, - 'submission_id' : submission_id, - 'score' : score, - 'feedback' : feedback, + data = {'grader_id': grader_id, + 'submission_id': submission_id, + 'score': score, + 'feedback': feedback, 'submission_key': submission_key, 'location': location, 'rubric_scores': rubric_scores, 'rubric_scores_complete': True, - 'submission_flagged' : submission_flagged} + 'submission_flagged': submission_flagged} return self.try_to_decode(self.post(self.save_grade_url, data)) def is_student_calibrated(self, problem_location, grader_id): - params = {'problem_id' : problem_location, 'student_id': grader_id} + params = {'problem_id': problem_location, 'student_id': grader_id} return self.try_to_decode(self.get(self.is_student_calibrated_url, params)) def show_calibration_essay(self, problem_location, grader_id): - params = {'problem_id' : problem_location, 'student_id': grader_id} + params = {'problem_id': problem_location, 'student_id': grader_id} response = self.get(self.show_calibration_essay_url, params) return self.try_to_decode(self._render_rubric(response)) @@ -100,10 +102,12 @@ class PeerGradingService(GradingService): This is a mock peer grading service that can be used for unit tests without making actual service calls to the grading controller """ + + class MockPeerGradingService(object): def get_next_submission(self, problem_location, grader_id): return json.dumps({'success': True, - 'submission_id':1, + 'submission_id': 1, 'submission_key': "", 'student_response': 'fake student response', 'prompt': 'fake submission prompt', @@ -119,7 +123,7 @@ class MockPeerGradingService(object): def show_calibration_essay(self, problem_location, grader_id): return json.dumps({'success': True, - 'submission_id':1, + 'submission_id': 1, 'submission_key': '', 'student_response': 'fake student response', 'prompt': 'fake submission prompt', @@ -140,6 +144,8 @@ class MockPeerGradingService(object): ]}) _service = None + + def peer_grading_service(system): """ Return a peer grading service instance--if settings.MOCK_PEER_GRADING is True, diff --git a/common/lib/xmodule/xmodule/randomize_module.py b/common/lib/xmodule/xmodule/randomize_module.py index f88cdc5efb..b336789193 100644 --- a/common/lib/xmodule/xmodule/randomize_module.py +++ b/common/lib/xmodule/xmodule/randomize_module.py @@ -12,6 +12,7 @@ from pkg_resources import resource_string log = logging.getLogger('mitx.' + __name__) + class RandomizeModule(XModule): """ Chooses a random child module. Chooses the same one every time for each student. @@ -96,7 +97,7 @@ class RandomizeModule(XModule): def get_icon_class(self): return self.child.get_icon_class() if self.child else 'other' - + class RandomizeDescriptor(SequenceDescriptor): # the editing interface can be the same as for sequences -- just a container module_class = RandomizeModule @@ -118,4 +119,3 @@ class RandomizeDescriptor(SequenceDescriptor): makes it use module.get_child_descriptors(). """ return True - diff --git a/common/lib/xmodule/xmodule/raw_module.py b/common/lib/xmodule/xmodule/raw_module.py index efdd2e7ba0..4a2bfbceaf 100644 --- a/common/lib/xmodule/xmodule/raw_module.py +++ b/common/lib/xmodule/xmodule/raw_module.py @@ -6,6 +6,7 @@ import sys log = logging.getLogger(__name__) + class RawDescriptor(XmlDescriptor, XMLEditingDescriptor): """ Module that provides a raw editing view of its data and children. It @@ -13,7 +14,7 @@ class RawDescriptor(XmlDescriptor, XMLEditingDescriptor): """ @classmethod def definition_from_xml(cls, xml_object, system): - return {'data': etree.tostring(xml_object, pretty_print=True,encoding='unicode')} + return {'data': etree.tostring(xml_object, pretty_print=True, encoding='unicode')} def definition_to_xml(self, resource_fs): try: diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 38a60e11f5..07cc68a83a 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -25,6 +25,7 @@ from combined_open_ended_rubric import CombinedOpenEndedRubric log = logging.getLogger("mitx.courseware") + class SelfAssessmentModule(openendedchild.OpenEndedChild): """ A Self Assessment module that allows students to write open-ended responses, diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 6dd92cc8fa..36011744f5 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -88,8 +88,8 @@ class SequenceModule(XModule): 'type': child.get_icon_class(), 'id': child.id, } - if childinfo['title']=='': - childinfo['title'] = child.metadata.get('display_name','') + if childinfo['title'] == '': + childinfo['title'] = child.metadata.get('display_name', '') contents.append(childinfo) params = {'items': contents, @@ -116,7 +116,7 @@ class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor): mako_template = 'widgets/sequence-edit.html' module_class = SequenceModule - stores_state = True # For remembering where in the sequence the student is + stores_state = True # For remembering where in the sequence the student is js = {'coffee': [resource_string(__name__, 'js/src/sequence/edit.coffee')]} js_module_name = "SequenceDescriptor" @@ -140,4 +140,3 @@ class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor): xml_object.append( etree.fromstring(child.export_to_xml(resource_fs))) return xml_object - diff --git a/common/lib/xmodule/xmodule/stringify.py b/common/lib/xmodule/xmodule/stringify.py index dab8ff0425..5a640e91b1 100644 --- a/common/lib/xmodule/xmodule/stringify.py +++ b/common/lib/xmodule/xmodule/stringify.py @@ -1,6 +1,7 @@ from itertools import chain from lxml import etree + def stringify_children(node): ''' Return all contents of an xml tree, without the outside tags. diff --git a/common/lib/xmodule/xmodule/template_module.py b/common/lib/xmodule/xmodule/template_module.py index d7e7ece897..5f376945eb 100644 --- a/common/lib/xmodule/xmodule/template_module.py +++ b/common/lib/xmodule/xmodule/template_module.py @@ -5,6 +5,7 @@ from mako.template import Template from xmodule.modulestore.django import modulestore import logging + class CustomTagModule(XModule): """ This module supports tags of the form @@ -81,4 +82,3 @@ class CustomTagDescriptor(RawDescriptor): to export them in a file with yet another layer of indirection. """ return False - diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index 1f323834a9..04e7ee19b1 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -29,9 +29,9 @@ test_system = ModuleSystem( user=Mock(is_staff=False), filestore=Mock(), debug=True, - xqueue={'interface':None, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 10}, + xqueue={'interface': None, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 10}, node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"), - anonymous_student_id = 'student' + anonymous_student_id='student' ) @@ -85,4 +85,3 @@ class ModelsTest(unittest.TestCase): except: exception_happened = True self.assertTrue(exception_happened) - diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index e8f639e3c9..a22fcdb5f6 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -10,6 +10,7 @@ from lxml import etree from . import test_system + class CapaFactory(object): """ A helper class to create problem modules with various parameters for testing. @@ -58,7 +59,7 @@ class CapaFactory(object): attempts: also added to instance state. Will be converted to an int. """ - definition = {'data': CapaFactory.sample_problem_xml,} + definition = {'data': CapaFactory.sample_problem_xml, } location = Location(["i4x", "edX", "capa_test", "problem", "SampleProblem{0}".format(CapaFactory.next_num())]) metadata = {} @@ -208,8 +209,3 @@ class CapaModuleTest(unittest.TestCase): due=self.yesterday_str, graceperiod=self.two_day_delta_str) self.assertFalse(still_in_grace.answer_available()) - - - - - diff --git a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py index c89f5ee848..690eb7e39e 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -20,6 +20,7 @@ OpenEndedModule """ + class OpenEndedChildTest(unittest.TestCase): location = Location(["i4x", "edX", "sa_test", "selfassessment", "SampleQuestion"]) @@ -38,17 +39,17 @@ class OpenEndedChildTest(unittest.TestCase): 'max_attempts': 20, 'prompt': prompt, 'rubric': rubric, - 'max_score': max_score, + 'max_score': max_score, 'display_name': 'Name', - 'accept_file_upload' : False, + 'accept_file_upload': False, } definition = Mock() descriptor = Mock() def setUp(self): - self.openendedchild = OpenEndedChild(test_system, self.location, + self.openendedchild = OpenEndedChild(test_system, self.location, self.definition, self.descriptor, self.static_data, self.metadata) - + def test_latest_answer_empty(self): answer = self.openendedchild.latest_answer() @@ -115,12 +116,12 @@ class OpenEndedChildTest(unittest.TestCase): self.assertEqual(score['score'], new_score) self.assertEqual(score['total'], self.static_data['max_score']) - + def test_reset(self): self.openendedchild.reset(test_system) state = json.loads(self.openendedchild.get_instance_state()) self.assertEqual(state['state'], OpenEndedChild.INITIAL) - + def test_is_last_response_correct(self): new_answer = "New Answer" @@ -134,6 +135,7 @@ class OpenEndedChildTest(unittest.TestCase): self.assertEqual(self.openendedchild.is_last_response_correct(), 'incorrect') + class OpenEndedModuleTest(unittest.TestCase): location = Location(["i4x", "edX", "sa_test", "selfassessment", "SampleQuestion"]) @@ -152,7 +154,7 @@ class OpenEndedModuleTest(unittest.TestCase): 'max_attempts': 20, 'prompt': prompt, 'rubric': rubric, - 'max_score': max_score, + 'max_score': max_score, 'display_name': 'Name', 'accept_file_upload': False, } @@ -170,9 +172,9 @@ class OpenEndedModuleTest(unittest.TestCase): def setUp(self): test_system.location = self.location self.mock_xqueue = MagicMock() - self.mock_xqueue.send_to_queue.return_value=(None, "Message") - test_system.xqueue = {'interface':self.mock_xqueue, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 1} - self.openendedmodule = OpenEndedModule(test_system, self.location, + self.mock_xqueue.send_to_queue.return_value = (None, "Message") + test_system.xqueue = {'interface': self.mock_xqueue, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 1} + self.openendedmodule = OpenEndedModule(test_system, self.location, self.definition, self.descriptor, self.static_data, self.metadata) def test_message_post(self): @@ -194,8 +196,8 @@ class OpenEndedModuleTest(unittest.TestCase): result = self.openendedmodule.message_post(get, test_system) self.assertTrue(result['success']) # make sure it's actually sending something we want to the queue - self.mock_xqueue.send_to_queue.assert_called_with(body = json.dumps(contents), header=ANY) - + self.mock_xqueue.send_to_queue.assert_called_with(body=json.dumps(contents), header=ANY) + state = json.loads(self.openendedmodule.get_instance_state()) self.assertIsNotNone(state['state'], OpenEndedModule.DONE) @@ -205,21 +207,21 @@ class OpenEndedModuleTest(unittest.TestCase): student_info = {'anonymous_student_id': test_system.anonymous_student_id, 'submission_time': qtime} contents = self.openendedmodule.payload.copy() - contents.update({ + contents.update({ 'student_info': json.dumps(student_info), - 'student_response': submission, + 'student_response': submission, 'max_score': self.max_score }) result = self.openendedmodule.send_to_grader(submission, test_system) self.assertTrue(result) - self.mock_xqueue.send_to_queue.assert_called_with(body = json.dumps(contents), header=ANY) + self.mock_xqueue.send_to_queue.assert_called_with(body=json.dumps(contents), header=ANY) def update_score_single(self): self.openendedmodule.new_history_entry("New Entry") - score_msg = { + score_msg = { 'correct': True, 'score': 4, - 'msg' : 'Grader Message', + 'msg': 'Grader Message', 'feedback': "Grader Feedback" } get = {'queuekey': "abcd", @@ -232,10 +234,10 @@ class OpenEndedModuleTest(unittest.TestCase): "success": True, "feedback": "Grader Feedback" } - score_msg = { + score_msg = { 'correct': True, 'score': 4, - 'msg' : 'Grader Message', + 'msg': 'Grader Message', 'feedback': json.dumps(feedback), 'grader_type': 'IN', 'grader_id': '1', @@ -261,6 +263,7 @@ class OpenEndedModuleTest(unittest.TestCase): score = self.openendedmodule.latest_score() self.assertEqual(score, 4) + class CombinedOpenEndedModuleTest(unittest.TestCase): location = Location(["i4x", "edX", "open_ended", "combinedopenended", "SampleQuestion"]) @@ -280,7 +283,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): 'max_attempts': 20, 'prompt': prompt, 'rubric': rubric, - 'max_score': max_score, + 'max_score': max_score, 'display_name': 'Name' }) @@ -335,5 +338,3 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): changed = self.combinedoe.update_task_states() self.assertTrue(changed) - - diff --git a/common/lib/xmodule/xmodule/tests/test_conditional.py b/common/lib/xmodule/xmodule/tests/test_conditional.py index f889ec7111..1b463eccaf 100644 --- a/common/lib/xmodule/xmodule/tests/test_conditional.py +++ b/common/lib/xmodule/xmodule/tests/test_conditional.py @@ -17,10 +17,11 @@ from xmodule.modulestore.exceptions import ItemNotFoundError from .test_export import DATA_DIR ORG = 'test_org' -COURSE = 'conditional' # name of directory with course data +COURSE = 'conditional' # name of directory with course data from . import test_system + class DummySystem(ImportSystem): @patch('xmodule.modulestore.xml.OSFS', lambda dir: MemoryFS()) @@ -82,11 +83,11 @@ class ConditionalModuleTest(unittest.TestCase): location = descriptor descriptor = self.modulestore.get_instance(course.id, location, depth=None) location = descriptor.location - instance_state = instance_states.get(location.category,None) + instance_state = instance_states.get(location.category, None) print "inner_get_module, location.category=%s, inst_state=%s" % (location.category, instance_state) return descriptor.xmodule_constructor(test_system)(instance_state, shared_state) - location = Location(["i4x", "edX", "cond_test", "conditional","condone"]) + location = Location(["i4x", "edX", "cond_test", "conditional", "condone"]) module = inner_get_module(location) def replace_urls(text, staticfiles_prefix=None, replace_prefix='/static/', course_namespace=None): @@ -105,15 +106,13 @@ class ConditionalModuleTest(unittest.TestCase): gdi = module.get_display_items() print "gdi=", gdi - ajax = json.loads(module.handle_ajax('','')) + ajax = json.loads(module.handle_ajax('', '')) self.assertTrue('xmodule.conditional_module' in ajax['html']) print "ajax: ", ajax # now change state of the capa problem to make it completed - instance_states['problem'] = json.dumps({'attempts':1}) + instance_states['problem'] = json.dumps({'attempts': 1}) - ajax = json.loads(module.handle_ajax('','')) + ajax = json.loads(module.handle_ajax('', '')) self.assertTrue('This is a secret' in ajax['html']) print "post-attempt ajax: ", ajax - - diff --git a/common/lib/xmodule/xmodule/tests/test_content.py b/common/lib/xmodule/xmodule/tests/test_content.py index 6bd10f22f7..1bcd2f4ebe 100644 --- a/common/lib/xmodule/xmodule/tests/test_content.py +++ b/common/lib/xmodule/xmodule/tests/test_content.py @@ -3,11 +3,13 @@ from xmodule.contentstore.content import StaticContent from xmodule.contentstore.content import ContentStore from xmodule.modulestore import Location + class Content: def __init__(self, location, content_type): self.location = location self.content_type = content_type + class ContentTest(unittest.TestCase): def test_thumbnail_none(self): # We had a bug where a thumbnail location of None was getting transformed into a Location tuple, with @@ -22,4 +24,4 @@ class ContentTest(unittest.TestCase): content = Content(Location(u'c4x', u'mitX', u'800', u'asset', u'monsters.jpg'), None) (thumbnail_content, thumbnail_file_location) = contentStore.generate_thumbnail(content) self.assertIsNone(thumbnail_content) - self.assertEqual(Location(u'c4x', u'mitX', u'800', u'thumbnail', u'monsters.jpg'), thumbnail_file_location) \ No newline at end of file + self.assertEqual(Location(u'c4x', u'mitX', u'800', u'thumbnail', u'monsters.jpg'), thumbnail_file_location) diff --git a/common/lib/xmodule/xmodule/tests/test_export.py b/common/lib/xmodule/xmodule/tests/test_export.py index f92d58db03..da1b04bd94 100644 --- a/common/lib/xmodule/xmodule/tests/test_export.py +++ b/common/lib/xmodule/xmodule/tests/test_export.py @@ -27,6 +27,7 @@ def strip_metadata(descriptor, key): for d in descriptor.get_children(): strip_metadata(d, key) + def strip_filenames(descriptor): """ Recursively strips 'filename' from all children's definitions. @@ -119,12 +120,12 @@ class RoundTripTestCase(unittest.TestCase): def test_selfassessment_roundtrip(self): #Test selfassessment xmodule to see if it exports correctly - self.check_export_roundtrip(DATA_DIR,"self_assessment") + self.check_export_roundtrip(DATA_DIR, "self_assessment") def test_graphicslidertool_roundtrip(self): #Test graphicslidertool xmodule to see if it exports correctly - self.check_export_roundtrip(DATA_DIR,"graphic_slider_tool") + self.check_export_roundtrip(DATA_DIR, "graphic_slider_tool") def test_exam_registration_roundtrip(self): # Test exam_registration xmodule to see if it exports correctly - self.check_export_roundtrip(DATA_DIR,"test_exam_registration") + self.check_export_roundtrip(DATA_DIR, "test_exam_registration") diff --git a/common/lib/xmodule/xmodule/tests/test_graders.py b/common/lib/xmodule/xmodule/tests/test_graders.py index fa0e94d2d5..27416b1d5c 100644 --- a/common/lib/xmodule/xmodule/tests/test_graders.py +++ b/common/lib/xmodule/xmodule/tests/test_graders.py @@ -4,6 +4,7 @@ import unittest from xmodule import graders from xmodule.graders import Score, aggregate_scores + class GradesheetTest(unittest.TestCase): def test_weighted_grading(self): @@ -217,4 +218,3 @@ class GraderTest(unittest.TestCase): #TODO: How do we test failure cases? The parser only logs an error when #it can't parse something. Maybe it should throw exceptions? - diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py index 7cd91223e3..42072ffe4d 100644 --- a/common/lib/xmodule/xmodule/tests/test_import.py +++ b/common/lib/xmodule/xmodule/tests/test_import.py @@ -61,6 +61,7 @@ class BaseCourseTestCase(unittest.TestCase): self.assertEquals(len(courses), 1) return courses[0] + class ImportTestCase(BaseCourseTestCase): def test_fallback(self): @@ -323,7 +324,7 @@ class ImportTestCase(BaseCourseTestCase): self.assertEqual(len(sections), 4) - for i in (2,3): + for i in (2, 3): video = sections[i] # Name should be 'video_{hash}' print "video {0} url_name: {1}".format(i, video.url_name) @@ -379,5 +380,3 @@ class ImportTestCase(BaseCourseTestCase): # and finally... course.metadata['cohort_config'] = {'cohorted': True} self.assertTrue(course.is_cohorted) - - diff --git a/common/lib/xmodule/xmodule/tests/test_progress.py b/common/lib/xmodule/xmodule/tests/test_progress.py index cb011cdc2b..0114ba4ad3 100644 --- a/common/lib/xmodule/xmodule/tests/test_progress.py +++ b/common/lib/xmodule/xmodule/tests/test_progress.py @@ -7,6 +7,7 @@ from xmodule import x_module from . import test_system + class ProgressTest(unittest.TestCase): ''' Test that basic Progress objects work. A Progress represents a fraction between 0 and 1. diff --git a/common/lib/xmodule/xmodule/tests/test_randomize_module.py b/common/lib/xmodule/xmodule/tests/test_randomize_module.py index 6353245f1a..456fd379a5 100644 --- a/common/lib/xmodule/xmodule/tests/test_randomize_module.py +++ b/common/lib/xmodule/xmodule/tests/test_randomize_module.py @@ -52,4 +52,3 @@ class RandomizeModuleTestCase(unittest.TestCase): # TODO: add tests that create a module and check. Passing state is a good way to # check that child access works... - diff --git a/common/lib/xmodule/xmodule/tests/test_self_assessment.py b/common/lib/xmodule/xmodule/tests/test_self_assessment.py index c5fb82e412..78dbb082ac 100644 --- a/common/lib/xmodule/xmodule/tests/test_self_assessment.py +++ b/common/lib/xmodule/xmodule/tests/test_self_assessment.py @@ -8,6 +8,7 @@ from lxml import etree from . import test_system + class SelfAssessmentTest(unittest.TestCase): rubric = ''' @@ -44,7 +45,7 @@ class SelfAssessmentTest(unittest.TestCase): 'prompt': self.prompt, 'max_score': 1, 'display_name': "Name", - 'accept_file_upload' : False, + 'accept_file_upload': False, } self.module = SelfAssessmentModule(test_system, self.location, @@ -74,4 +75,3 @@ class SelfAssessmentTest(unittest.TestCase): self.module.save_answer({'student_answer': 'answer 4'}, test_system) self.module.save_assessment({'assessment': '1'}, test_system) self.assertEqual(self.module.state, self.module.DONE) - diff --git a/common/lib/xmodule/xmodule/tests/test_stringify.py b/common/lib/xmodule/xmodule/tests/test_stringify.py index 29e99bef56..e44b93b0b8 100644 --- a/common/lib/xmodule/xmodule/tests/test_stringify.py +++ b/common/lib/xmodule/xmodule/tests/test_stringify.py @@ -2,6 +2,7 @@ from nose.tools import assert_equals, assert_true, assert_false from lxml import etree from xmodule.stringify import stringify_children + def test_stringify(): text = 'Hi
            there Bruce!
            ' html = '''{0}'''.format(text) @@ -9,6 +10,7 @@ def test_stringify(): out = stringify_children(xml) assert_equals(out, text) + def test_stringify_again(): html = """A voltage source is non-linear!
            diff --git a/common/lib/xmodule/xmodule/timeparse.py b/common/lib/xmodule/xmodule/timeparse.py index 36c0f725e5..1c3a780ad8 100644 --- a/common/lib/xmodule/xmodule/timeparse.py +++ b/common/lib/xmodule/xmodule/timeparse.py @@ -5,6 +5,7 @@ import time TIME_FORMAT = "%Y-%m-%dT%H:%M" + def parse_time(time_str): """ Takes a time string in TIME_FORMAT @@ -15,6 +16,7 @@ def parse_time(time_str): """ return time.strptime(time_str, TIME_FORMAT) + def stringify_time(time_struct): """ Convert a time struct to a string diff --git a/common/lib/xmodule/xmodule/util/decorators.py b/common/lib/xmodule/xmodule/util/decorators.py index 81ab747a3e..0b9b301244 100644 --- a/common/lib/xmodule/xmodule/util/decorators.py +++ b/common/lib/xmodule/xmodule/util/decorators.py @@ -4,8 +4,8 @@ def lazyproperty(fn): """ Use this decorator for lazy generation of properties that are expensive to compute. From http://stackoverflow.com/a/3013910/86828 - - + + Example: class Test(object): @@ -13,7 +13,7 @@ def lazyproperty(fn): def a(self): print 'generating "a"' return range(5) - + Interactive Session: >>> t = Test() >>> t.__dict__ @@ -26,11 +26,11 @@ def lazyproperty(fn): >>> t.a [0, 1, 2, 3, 4] """ - + attr_name = '_lazy_' + fn.__name__ @property def _lazyprop(self): if not hasattr(self, attr_name): setattr(self, attr_name, fn(self)) return getattr(self, attr_name) - return _lazyprop \ No newline at end of file + return _lazyprop diff --git a/common/lib/xmodule/xmodule/vertical_module.py b/common/lib/xmodule/xmodule/vertical_module.py index 14105b41d0..5827ea96a9 100644 --- a/common/lib/xmodule/xmodule/vertical_module.py +++ b/common/lib/xmodule/xmodule/vertical_module.py @@ -47,6 +47,6 @@ class VerticalDescriptor(SequenceDescriptor): js = {'coffee': [resource_string(__name__, 'js/src/vertical/edit.coffee')]} js_module_name = "VerticalDescriptor" - + # TODO (victor): Does this need its own definition_to_xml method? Otherwise it looks # like verticals will get exported as sequentials... diff --git a/common/lib/xmodule/xmodule/video_module.py b/common/lib/xmodule/xmodule/video_module.py index f21cd37a37..6d9715cfd6 100644 --- a/common/lib/xmodule/xmodule/video_module.py +++ b/common/lib/xmodule/xmodule/video_module.py @@ -121,7 +121,7 @@ class VideoModule(XModule): return self.youtube def get_html(self): - if isinstance(modulestore(), XMLModuleStore) : + if isinstance(modulestore(), XMLModuleStore): # VS[compat] # cdodge: filesystem static content support. caption_asset_path = "/static/{0}/subs/".format(self.metadata['data_dir']) diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 0e4e8e0f00..87c085b19a 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -109,16 +109,16 @@ class HTMLSnippet(object): All of these will be loaded onto the page in the CMS """ # cdodge: We've moved the xmodule.coffee script from an outside directory into the xmodule area of common - # this means we need to make sure that all xmodules include this dependency which had been previously implicitly + # this means we need to make sure that all xmodules include this dependency which had been previously implicitly # fulfilled in a different area of code js = cls.js - + if js is None: js = {} if 'coffee' not in js: js['coffee'] = [] - + js['coffee'].append(resource_string(__name__, 'js/src/xmodule.coffee')) return js @@ -538,7 +538,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates): def start(self, value): if isinstance(value, time.struct_time): self.metadata['start'] = stringify_time(value) - + @property def days_early_for_beta(self): """ diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index ed2f6ce921..64c3aabbcc 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -16,6 +16,7 @@ edx_xml_parser = etree.XMLParser(dtd_validation=False, load_dtd=False, remove_comments=True, remove_blank_text=True, encoding='utf-8') + def name_to_pathname(name): """ Convert a location name for use in a path: replace ':' with '/'. @@ -23,6 +24,7 @@ def name_to_pathname(name): """ return name.replace(':', '/') + def is_pointer_tag(xml_obj): """ Check if xml_obj is a pointer tag: . @@ -46,6 +48,7 @@ def is_pointer_tag(xml_obj): return len(xml_obj) == 0 and actual_attr == expected_attr and not has_text + def get_metadata_from_xml(xml_object, remove=True): meta = xml_object.find('meta') if meta is None: @@ -58,6 +61,7 @@ def get_metadata_from_xml(xml_object, remove=True): _AttrMapBase = namedtuple('_AttrMap', 'from_xml to_xml') + class AttrMap(_AttrMapBase): """ A class that specifies two functions: @@ -93,16 +97,16 @@ class XmlDescriptor(XModuleDescriptor): metadata_attributes = ('format', 'graceperiod', 'showanswer', 'rerandomize', 'start', 'due', 'graded', 'display_name', 'url_name', 'hide_from_toc', 'ispublic', # if True, then course is listed for all users; see - 'xqa_key', # for xqaa server access - # information about testcenter exams is a dict (of dicts), not a string, - # so it cannot be easily exportable as a course element's attribute. + 'xqa_key', # for xqaa server access + # information about testcenter exams is a dict (of dicts), not a string, + # so it cannot be easily exportable as a course element's attribute. 'testcenter_info', # VS[compat] Remove once unused. 'name', 'slug') - metadata_to_strip = ('data_dir', + metadata_to_strip = ('data_dir', # cdodge: @TODO: We need to figure out a way to export out 'tabs' and 'grading_policy' which is on the course - 'tabs', 'grading_policy', 'is_draft', 'published_by', 'published_date', + 'tabs', 'grading_policy', 'is_draft', 'published_by', 'published_date', 'discussion_blackouts', 'testcenter_info', # VS[compat] -- remove the below attrs once everything is in the CMS 'course', 'org', 'url_name', 'filename') @@ -117,7 +121,7 @@ class XmlDescriptor(XModuleDescriptor): bool_map = AttrMap(to_bool, from_bool) to_int = lambda val: int(val) - from_int = lambda val: str(val) + from_int = lambda val: str(val) int_map = AttrMap(to_int, from_int) xml_attribute_map = { # type conversion: want True/False in python, "true"/"false" in xml @@ -125,7 +129,7 @@ class XmlDescriptor(XModuleDescriptor): 'hide_progress_tab': bool_map, 'allow_anonymous': bool_map, 'allow_anonymous_to_peers': bool_map, - 'weight':int_map + 'weight': int_map } @@ -133,8 +137,8 @@ class XmlDescriptor(XModuleDescriptor): # importing 2012 courses. # A set of metadata key conversions that we want to make metadata_translations = { - 'slug' : 'url_name', - 'name' : 'display_name', + 'slug': 'url_name', + 'name': 'display_name', } @classmethod @@ -230,7 +234,7 @@ class XmlDescriptor(XModuleDescriptor): # TODO (ichuang): remove this after migration # for Fall 2012 LMS migration: keep filename (and unmangled filename) - definition['filename'] = [ filepath, filename ] + definition['filename'] = [filepath, filename] return definition @@ -291,9 +295,9 @@ class XmlDescriptor(XModuleDescriptor): filepath = cls._format_filepath(xml_object.tag, name_to_pathname(url_name)) definition_xml = cls.load_file(filepath, system.resources_fs, location) else: - definition_xml = xml_object # this is just a pointer, not the real definition content + definition_xml = xml_object # this is just a pointer, not the real definition content - definition = cls.load_definition(definition_xml, system, location) # note this removes metadata + definition = cls.load_definition(definition_xml, system, location) # note this removes metadata # VS[compat] -- make Ike's github preview links work in both old and # new file layouts if is_pointer_tag(xml_object): @@ -303,13 +307,13 @@ class XmlDescriptor(XModuleDescriptor): metadata = cls.load_metadata(definition_xml) # move definition metadata into dict - dmdata = definition.get('definition_metadata','') + dmdata = definition.get('definition_metadata', '') if dmdata: metadata['definition_metadata_raw'] = dmdata try: metadata.update(json.loads(dmdata)) except Exception as err: - log.debug('Error %s in loading metadata %s' % (err,dmdata)) + log.debug('Error %s in loading metadata %s' % (err, dmdata)) metadata['definition_metadata_err'] = str(err) # Set/override any metadata specified by policy diff --git a/common/static/js/vendor/mathjax-MathJax-c9db6ac/docs/source/conf.py b/common/static/js/vendor/mathjax-MathJax-c9db6ac/docs/source/conf.py index f0a8013eed..819bff31aa 100644 --- a/common/static/js/vendor/mathjax-MathJax-c9db6ac/docs/source/conf.py +++ b/common/static/js/vendor/mathjax-MathJax-c9db6ac/docs/source/conf.py @@ -11,7 +11,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import sys +import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the diff --git a/common/xml_cleanup.py b/common/xml_cleanup.py index 15890fb99e..5f2b527063 100755 --- a/common/xml_cleanup.py +++ b/common/xml_cleanup.py @@ -8,12 +8,16 @@ In particular, the remove-meta option is only intended to be used after pulling using the metadata_to_json management command. """ -import os, fnmatch, re, sys +import os +import fnmatch +import re +import sys from lxml import etree from collections import defaultdict INVALID_CHARS = re.compile(r"[^\w.-]") + def clean(value): """ Return value, made into a form legal for locations @@ -24,6 +28,7 @@ def clean(value): # category -> set of url_names for that category that we've already seen used_names = defaultdict(set) + def clean_unique(category, name): cleaned = clean(name) if cleaned not in used_names[category]: @@ -38,6 +43,7 @@ def clean_unique(category, name): used_names[category].add(cleaned) return cleaned + def cleanup(filepath, remove_meta): # Keys that are exported to the policy file, and so # can be removed from the xml afterward @@ -70,7 +76,7 @@ def cleanup(filepath, remove_meta): print "WARNING: {0} has both slug and url_name".format(node) if ('url_name' in attrs and 'filename' in attrs and - len(attrs)==2 and attrs['url_name'] == attrs['filename']): + len(attrs) == 2 and attrs['url_name'] == attrs['filename']): # This is a pointer tag in disguise. Get rid of the filename. print 'turning {0}.{1} into a pointer tag'.format(node.tag, attrs['url_name']) del attrs['filename'] @@ -108,5 +114,3 @@ def main(args): if __name__ == '__main__': main(sys.argv[1:]) - - diff --git a/lms/djangoapps/certificates/migrations/0008_auto__del_revokedcertificate__del_field_generatedcertificate_name__add.py b/lms/djangoapps/certificates/migrations/0008_auto__del_revokedcertificate__del_field_generatedcertificate_name__add.py index cbec7214c0..872804d286 100644 --- a/lms/djangoapps/certificates/migrations/0008_auto__del_revokedcertificate__del_field_generatedcertificate_name__add.py +++ b/lms/djangoapps/certificates/migrations/0008_auto__del_revokedcertificate__del_field_generatedcertificate_name__add.py @@ -160,4 +160,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['certificates'] \ No newline at end of file + complete_apps = ['certificates'] diff --git a/lms/djangoapps/certificates/migrations/0009_auto__del_field_generatedcertificate_graded_download_url__del_field_ge.py b/lms/djangoapps/certificates/migrations/0009_auto__del_field_generatedcertificate_graded_download_url__del_field_ge.py index 40637452cc..2ff1434314 100644 --- a/lms/djangoapps/certificates/migrations/0009_auto__del_field_generatedcertificate_graded_download_url__del_field_ge.py +++ b/lms/djangoapps/certificates/migrations/0009_auto__del_field_generatedcertificate_graded_download_url__del_field_ge.py @@ -92,4 +92,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['certificates'] \ No newline at end of file + complete_apps = ['certificates'] diff --git a/lms/djangoapps/certificates/migrations/0010_auto__del_field_generatedcertificate_enabled__add_field_generatedcerti.py b/lms/djangoapps/certificates/migrations/0010_auto__del_field_generatedcertificate_enabled__add_field_generatedcerti.py index 5970c96f6b..a41e58cc3b 100644 --- a/lms/djangoapps/certificates/migrations/0010_auto__del_field_generatedcertificate_enabled__add_field_generatedcerti.py +++ b/lms/djangoapps/certificates/migrations/0010_auto__del_field_generatedcertificate_enabled__add_field_generatedcerti.py @@ -78,4 +78,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['certificates'] \ No newline at end of file + complete_apps = ['certificates'] diff --git a/lms/djangoapps/certificates/migrations/0011_auto__del_field_generatedcertificate_certificate_id__add_field_generat.py b/lms/djangoapps/certificates/migrations/0011_auto__del_field_generatedcertificate_certificate_id__add_field_generat.py index 36d6e5d4f3..155839a82f 100644 --- a/lms/djangoapps/certificates/migrations/0011_auto__del_field_generatedcertificate_certificate_id__add_field_generat.py +++ b/lms/djangoapps/certificates/migrations/0011_auto__del_field_generatedcertificate_certificate_id__add_field_generat.py @@ -87,4 +87,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['certificates'] \ No newline at end of file + complete_apps = ['certificates'] diff --git a/lms/djangoapps/certificates/migrations/0012_auto__add_field_generatedcertificate_name__add_field_generatedcertific.py b/lms/djangoapps/certificates/migrations/0012_auto__add_field_generatedcertificate_name__add_field_generatedcertific.py index 4195860ca5..9261654ec8 100644 --- a/lms/djangoapps/certificates/migrations/0012_auto__add_field_generatedcertificate_name__add_field_generatedcertific.py +++ b/lms/djangoapps/certificates/migrations/0012_auto__add_field_generatedcertificate_name__add_field_generatedcertific.py @@ -90,4 +90,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['certificates'] \ No newline at end of file + complete_apps = ['certificates'] diff --git a/lms/djangoapps/certificates/migrations/0013_auto__add_field_generatedcertificate_error_reason.py b/lms/djangoapps/certificates/migrations/0013_auto__add_field_generatedcertificate_error_reason.py index 7f31e6ebd9..6031c8055b 100644 --- a/lms/djangoapps/certificates/migrations/0013_auto__add_field_generatedcertificate_error_reason.py +++ b/lms/djangoapps/certificates/migrations/0013_auto__add_field_generatedcertificate_error_reason.py @@ -75,4 +75,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['certificates'] \ No newline at end of file + complete_apps = ['certificates'] diff --git a/lms/djangoapps/certificates/migrations/0014_adding_whitelist.py b/lms/djangoapps/certificates/migrations/0014_adding_whitelist.py index 1bd4d994cf..0aafea4067 100644 --- a/lms/djangoapps/certificates/migrations/0014_adding_whitelist.py +++ b/lms/djangoapps/certificates/migrations/0014_adding_whitelist.py @@ -86,4 +86,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['certificates'] \ No newline at end of file + complete_apps = ['certificates'] diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 0e68e3cfe7..dc438b805a 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -62,6 +62,7 @@ class CertificateStatuses(object): restricted = 'restricted' error = 'error' + class CertificateWhitelist(models.Model): """ Tracks students who are whitelisted, all users @@ -74,6 +75,7 @@ class CertificateWhitelist(models.Model): course_id = models.CharField(max_length=255, blank=True, default='') whitelist = models.BooleanField(default=0) + class GeneratedCertificate(models.Model): user = models.ForeignKey(User) course_id = models.CharField(max_length=255, blank=True, default='') diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index d926035efd..a7eb4e3c81 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -142,7 +142,7 @@ class XQueueCertInterface(object): """ - VALID_STATUSES = [ status.generating, + VALID_STATUSES = [status.generating, status.unavailable, status.deleted, status.error, status.notpassing] diff --git a/lms/djangoapps/course_wiki/course_nav.py b/lms/djangoapps/course_wiki/course_nav.py index 122f9ebb54..434860a0f7 100644 --- a/lms/djangoapps/course_wiki/course_nav.py +++ b/lms/djangoapps/course_wiki/course_nav.py @@ -11,6 +11,7 @@ from courseware.courses import get_course_with_access IN_COURSE_WIKI_REGEX = r'/courses/(?P[^/]+/[^/]+/[^/]+)/wiki/(?P.*|)$' + class Middleware(object): """ This middleware is to keep the course nav bar above the wiki while @@ -18,68 +19,68 @@ class Middleware(object): If it intercepts a request for /wiki/.. that has a referrer in the form /courses/course_id/... it will redirect the user to the page /courses/course_id/wiki/... - + It is also possible that someone followed a link leading to a course that they don't have access to. In this case, we redirect them to the same page on the regular wiki. - + If we return a redirect, this middleware makes sure that the redirect keeps the student in the course. - + Finally, if the student is in the course viewing a wiki, we change the reverse() function to resolve wiki urls as a course wiki url by setting the _transform_url attribute on wiki.models.reverse. - + Forgive me Father, for I have hacked. """ - + def __init__(self): self.redirected = False - + def process_request(self, request): self.redirected = False wiki_reverse._transform_url = lambda url: url - + referer = request.META.get('HTTP_REFERER') destination = request.path - - + + if request.method == 'GET': new_destination = self.get_redirected_url(request.user, referer, destination) - + if new_destination != destination: # We mark that we generated this redirection, so we don't modify it again self.redirected = True return redirect(new_destination) - + course_match = re.match(IN_COURSE_WIKI_REGEX, destination) if course_match: course_id = course_match.group('course_id') prepend_string = '/courses/' + course_match.group('course_id') wiki_reverse._transform_url = lambda url: prepend_string + url - + return None - - + + def process_response(self, request, response): """ If this is a redirect response going to /wiki/*, then we might need to change it to be a redirect going to /courses/*/wiki*. """ - if not self.redirected and response.status_code == 302: #This is a redirect + if not self.redirected and response.status_code == 302: # This is a redirect referer = request.META.get('HTTP_REFERER') destination_url = response['LOCATION'] destination = urlparse(destination_url).path - + new_destination = self.get_redirected_url(request.user, referer, destination) - + if new_destination != destination: new_url = destination_url.replace(destination, new_destination) response['LOCATION'] = new_url - + return response - - + + def get_redirected_url(self, user, referer, destination): """ Returns None if the destination shouldn't be changed. @@ -87,14 +88,14 @@ class Middleware(object): if not referer: return destination referer_path = urlparse(referer).path - + path_match = re.match(r'^/wiki/(?P.*|)$', destination) if path_match: # We are going to the wiki. Check if we came from a course course_match = re.match(r'/courses/(?P[^/]+/[^/]+/[^/]+)/.*', referer_path) if course_match: course_id = course_match.group('course_id') - + # See if we are able to view the course. If we are, redirect to it try: course = get_course_with_access(user, course_id, 'load') @@ -102,9 +103,9 @@ class Middleware(object): except Http404: # Even though we came from the course, we can't see it. So don't worry about it. pass - + else: - # It is also possible we are going to a course wiki view, but we + # It is also possible we are going to a course wiki view, but we # don't have permission to see the course! course_match = re.match(IN_COURSE_WIKI_REGEX, destination) if course_match: @@ -117,9 +118,9 @@ class Middleware(object): except Http404: # We can't see the course, so redirect to the regular wiki return "/wiki/" + course_match.group('wiki_path') - + return destination - + def context_processor(request): """ @@ -129,21 +130,20 @@ def context_processor(request): then we add 'course' to the context. This allows the course nav bar to be shown. """ - + match = re.match(IN_COURSE_WIKI_REGEX, request.path) if match: course_id = match.group('course_id') - + try: course = get_course_with_access(request.user, course_id, 'load') staff_access = has_access(request.user, course, 'staff') - return {'course' : course, + return {'course': course, 'staff_access': staff_access} except Http404: # We couldn't access the course for whatever reason. It is too late to change # the URL here, so we just leave the course context. The middleware shouldn't # let this happen pass - + return {} - \ No newline at end of file diff --git a/lms/djangoapps/course_wiki/editors.py b/lms/djangoapps/course_wiki/editors.py index c1f5b733ad..2ca8260bfe 100644 --- a/lms/djangoapps/course_wiki/editors.py +++ b/lms/djangoapps/course_wiki/editors.py @@ -14,11 +14,11 @@ class CodeMirrorWidget(forms.Widget): def __init__(self, attrs=None): # The 'rows' and 'cols' attributes are required for HTML correctness. default_attrs = {'class': 'markItUp', - 'rows': '10', 'cols': '40',} + 'rows': '10', 'cols': '40', } if attrs: default_attrs.update(attrs) super(CodeMirrorWidget, self).__init__(default_attrs) - + def render(self, name, value, attrs=None): if value is None: value = '' @@ -34,10 +34,10 @@ class CodeMirrorWidget(forms.Widget): class CodeMirror(BaseEditor): editor_id = 'codemirror' - + def get_admin_widget(self, instance=None): return MarkItUpAdminWidget() - + def get_widget(self, instance=None): return CodeMirrorWidget() @@ -61,4 +61,3 @@ class CodeMirror(BaseEditor): "js/wiki/CodeMirror.init.js", "js/wiki/cheatsheet.js", ) - diff --git a/lms/djangoapps/course_wiki/plugins/markdownedx/__init__.py b/lms/djangoapps/course_wiki/plugins/markdownedx/__init__.py index 8845471eda..53fd3d4d3b 100644 --- a/lms/djangoapps/course_wiki/plugins/markdownedx/__init__.py +++ b/lms/djangoapps/course_wiki/plugins/markdownedx/__init__.py @@ -1,2 +1,2 @@ # Make sure wiki_plugin.py gets run. -from course_wiki.plugins.markdownedx.wiki_plugin import ExtendMarkdownPlugin \ No newline at end of file +from course_wiki.plugins.markdownedx.wiki_plugin import ExtendMarkdownPlugin diff --git a/lms/djangoapps/course_wiki/plugins/markdownedx/mdx_mathjax.py b/lms/djangoapps/course_wiki/plugins/markdownedx/mdx_mathjax.py index a9148511e3..b14803744b 100644 --- a/lms/djangoapps/course_wiki/plugins/markdownedx/mdx_mathjax.py +++ b/lms/djangoapps/course_wiki/plugins/markdownedx/mdx_mathjax.py @@ -28,4 +28,3 @@ class MathJaxExtension(markdown.Extension): def makeExtension(configs=None): return MathJaxExtension(configs) - diff --git a/lms/djangoapps/course_wiki/plugins/markdownedx/wiki_plugin.py b/lms/djangoapps/course_wiki/plugins/markdownedx/wiki_plugin.py index ffbe7b0a52..765ee57422 100644 --- a/lms/djangoapps/course_wiki/plugins/markdownedx/wiki_plugin.py +++ b/lms/djangoapps/course_wiki/plugins/markdownedx/wiki_plugin.py @@ -7,15 +7,15 @@ from wiki.core.plugins import registry as plugin_registry from course_wiki.plugins.markdownedx import mdx_circuit, mdx_mathjax, mdx_video + class ExtendMarkdownPlugin(BasePlugin): """ This plugin simply loads all of the markdown extensions we use in edX. """ - + markdown_extensions = [mdx_circuit.CircuitExtension(configs={}), #mdx_image.ImageExtension() , #This one doesn't work. Tries to import simplewiki.settings - mdx_mathjax.MathJaxExtension(configs={}) , + mdx_mathjax.MathJaxExtension(configs={}), mdx_video.VideoExtension(configs={})] plugin_registry.register(ExtendMarkdownPlugin) - diff --git a/lms/djangoapps/course_wiki/tests/__init__.py b/lms/djangoapps/course_wiki/tests/__init__.py index 8b13789179..e69de29bb2 100644 --- a/lms/djangoapps/course_wiki/tests/__init__.py +++ b/lms/djangoapps/course_wiki/tests/__init__.py @@ -1 +0,0 @@ - diff --git a/lms/djangoapps/course_wiki/tests/tests.py b/lms/djangoapps/course_wiki/tests/tests.py index f1c4fa4810..99f138f0bc 100644 --- a/lms/djangoapps/course_wiki/tests/tests.py +++ b/lms/djangoapps/course_wiki/tests/tests.py @@ -13,10 +13,10 @@ class WikiRedirectTestCase(PageLoader): def setUp(self): xmodule.modulestore.django._MODULESTORES = {} courses = modulestore().get_courses() - + def find_course(name): """Assumes the course is present""" - return [c for c in courses if c.location.course==name][0] + return [c for c in courses if c.location.course == name][0] self.full = find_course("full") self.toy = find_course("toy") @@ -29,91 +29,89 @@ class WikiRedirectTestCase(PageLoader): self.create_account('u2', self.instructor, self.password) self.activate_user(self.student) self.activate_user(self.instructor) - - - + + + def test_wiki_redirect(self): """ Test that requesting wiki URLs redirect properly to or out of classes. - - An enrolled in student going from /courses/edX/toy/2012_Fall/progress - to /wiki/some/fake/wiki/page/ will redirect to + + 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/ - + An unenrolled student going to /courses/edX/toy/2012_Fall/wiki/some/fake/wiki/page/ will be redirected to /wiki/some/fake/wiki/page/ - + """ self.login(self.student, self.password) - + self.enroll(self.toy) - - referer = reverse("progress", 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("progress", "wiki/some/fake/wiki/page/") - - resp = self.client.get( destination, HTTP_REFERER=referer) - self.assertEqual(resp.status_code, 302 ) - - self.assertEqual(resp['Location'], 'http://testserver' + redirected_to ) - - + + resp = self.client.get(destination, HTTP_REFERER=referer) + self.assertEqual(resp.status_code, 302) + + self.assertEqual(resp['Location'], 'http://testserver' + redirected_to) + + # Now we test that the student will be redirected away from that page if the course doesn't exist # We do this in the same test because we want to make sure the redirected_to is constructed correctly - + # This is a location like /courses/*/wiki/* , but with an invalid course ID - bad_course_wiki_page = redirected_to.replace( self.toy.location.course, "bad_course" ) - - resp = self.client.get( bad_course_wiki_page, HTTP_REFERER=referer) + bad_course_wiki_page = redirected_to.replace(self.toy.location.course, "bad_course") + + resp = self.client.get(bad_course_wiki_page, HTTP_REFERER=referer) self.assertEqual(resp.status_code, 302) - self.assertEqual(resp['Location'], 'http://testserver' + destination ) - - + self.assertEqual(resp['Location'], 'http://testserver' + destination) + + def create_course_page(self, course): """ Test that loading the course wiki page creates the wiki page. The user must be enrolled in the course to see the page. """ - - course_wiki_home = reverse('course_wiki', kwargs={'course_id' : course.id}) - referer = reverse("progress", kwargs={ 'course_id' : self.toy.id }) - + + course_wiki_home = reverse('course_wiki', kwargs={'course_id': course.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('progress', 'wiki/' + self.toy.wiki_slug + "/") - + ending_location = resp.redirect_chain[-1][0] ending_status = resp.redirect_chain[-1][1] - - self.assertEquals(ending_location, 'http://testserver' + course_wiki_page ) + + self.assertEquals(ending_location, 'http://testserver' + course_wiki_page) self.assertEquals(resp.status_code, 200) - + self.has_course_navigator(resp) - + def has_course_navigator(self, resp): """ Ensure that the response has the course navigator. """ - self.assertTrue( "course info" in resp.content.lower() ) - self.assertTrue( "courseware" in resp.content.lower() ) - - + self.assertTrue("course info" in resp.content.lower()) + self.assertTrue("courseware" in resp.content.lower()) + + def test_course_navigator(self): """" Test that going from a course page to a wiki page contains the course navigator. """ - + self.login(self.student, self.password) self.enroll(self.toy) self.create_course_page(self.toy) - - - course_wiki_page = reverse('wiki:get', kwargs={'path' : self.toy.wiki_slug + '/'}) - referer = reverse("courseware", kwargs={ 'course_id' : self.toy.id }) - + + + course_wiki_page = reverse('wiki:get', kwargs={'path': self.toy.wiki_slug + '/'}) + referer = reverse("courseware", kwargs={'course_id': self.toy.id}) + resp = self.client.get(course_wiki_page, follow=True, HTTP_REFERER=referer) - + self.has_course_navigator(resp) - - diff --git a/lms/djangoapps/course_wiki/views.py b/lms/djangoapps/course_wiki/views.py index 47112fc1d3..a088f8fc14 100644 --- a/lms/djangoapps/course_wiki/views.py +++ b/lms/djangoapps/course_wiki/views.py @@ -12,6 +12,7 @@ from courseware.courses import get_course_by_id log = logging.getLogger(__name__) + def root_create(request): """ In the edX wiki, we don't show the root_create view. Instead, we @@ -28,7 +29,7 @@ def course_wiki_redirect(request, course_id): example, "/6.002x") to keep things simple. """ course = get_course_by_id(course_id) - + course_slug = course.wiki_slug @@ -43,7 +44,7 @@ def course_wiki_redirect(request, course_id): except: pass - + valid_slug = True if not course_slug: log.exception("This course is improperly configured. The slug cannot be empty.") @@ -54,8 +55,8 @@ def course_wiki_redirect(request, course_id): if not valid_slug: return redirect("wiki:get", path="") - - + + # The wiki needs a Site object created. We make sure it exists here try: site = Site.objects.get_current() @@ -66,30 +67,30 @@ def course_wiki_redirect(request, course_id): new_site.save() if str(new_site.id) != str(settings.SITE_ID): raise ImproperlyConfigured("No site object was created and the SITE_ID doesn't match the newly created one. " + str(new_site.id) + "!=" + str(settings.SITE_ID)) - + try: urlpath = URLPath.get_by_path(course_slug, select_related=True) - - results = list( Article.objects.filter( id = urlpath.article.id ) ) + + results = list(Article.objects.filter(id=urlpath.article.id)) if results: article = results[0] else: article = None - + except (NoRootURL, URLPath.DoesNotExist): # We will create it in the next block urlpath = None article = None - + if not article: # create it root = get_or_create_root() - + if urlpath: # Somehow we got a urlpath without an article. Just delete it and # recerate it. urlpath.delete() - + urlpath = URLPath.create_article( root, course_slug, @@ -105,9 +106,9 @@ def course_wiki_redirect(request, course_id): 'other_read': True, 'other_write': True, }) - + return redirect("wiki:get", path=urlpath.path) - + def get_or_create_root(): """ @@ -121,12 +122,12 @@ def get_or_create_root(): return root except NoRootURL: pass - + starting_content = "\n".join(( "Welcome to the edX Wiki", "===", "Visit a course wiki to add an article.")) - + root = URLPath.create_root(title="Wiki", content=starting_content) article = root.article @@ -136,6 +137,5 @@ def get_or_create_root(): article.other_read = True article.other_write = False article.save() - + return root - diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index 475a708254..b41d231011 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -186,9 +186,9 @@ def _get_access_group_name_course_desc(course, action): ''' Return name of group which gives staff access to course. Only understands action = 'staff' and 'instructor' ''' - if action=='staff': + if action == 'staff': return _course_staff_group_name(course.location) - elif action=='instructor': + elif action == 'instructor': return _course_instructor_group_name(course.location) return [] @@ -367,6 +367,7 @@ def _course_staff_group_name(location, course_context=None): return 'staff_%s' % course_id + def course_beta_test_group_name(location): """ Get the name of the beta tester group for a location. Right now, that's @@ -462,6 +463,7 @@ def _adjust_start_date_for_beta_testers(user, descriptor): return descriptor.start + def _has_instructor_access_to_location(user, location, course_context=None): return _has_access_to_location(user, location, 'instructor', course_context) diff --git a/lms/djangoapps/courseware/admin.py b/lms/djangoapps/courseware/admin.py index f7e54d1800..9ef4c1de20 100644 --- a/lms/djangoapps/courseware/admin.py +++ b/lms/djangoapps/courseware/admin.py @@ -11,4 +11,3 @@ admin.site.register(StudentModule) admin.site.register(OfflineComputedGrade) admin.site.register(OfflineComputedGradeLog) - diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index ce29d69784..52346d7583 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -27,6 +27,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError log = logging.getLogger(__name__) + def get_request_for_thread(): """Walk up the stack, return the nearest first argument named "request".""" frame = None @@ -152,7 +153,7 @@ def get_course_about_section(course, section_key): request = get_request_for_thread() loc = course.location._replace(category='about', name=section_key) - course_module = get_module(request.user, request, loc, None, course.id, not_found_ok = True, wrap_xmodule_display = False) + course_module = get_module(request.user, request, loc, None, course.id, not_found_ok=True, wrap_xmodule_display=False) html = '' @@ -190,7 +191,7 @@ def get_course_info_section(request, cache, course, section_key): loc = Location(course.location.tag, course.location.org, course.location.course, 'course_info', section_key) - course_module = get_module(request.user, request, loc, cache, course.id, wrap_xmodule_display = False) + course_module = get_module(request.user, request, loc, cache, course.id, wrap_xmodule_display=False) html = '' if course_module is not None: @@ -259,7 +260,7 @@ def get_courses(user, domain=None): courses = branding.get_visible_courses(domain) courses = [c for c in courses if has_access(user, c, 'see_exists')] - courses = sorted(courses, key=lambda course:course.number) + courses = sorted(courses, key=lambda course: course.number) return courses diff --git a/lms/djangoapps/courseware/features/courses.py b/lms/djangoapps/courseware/features/courses.py index aecaa139ff..9b1316b00d 100644 --- a/lms/djangoapps/courseware/features/courses.py +++ b/lms/djangoapps/courseware/features/courses.py @@ -8,6 +8,8 @@ from logging import getLogger logger = getLogger(__name__) ## support functions + + def get_courses(): ''' Returns dict of lists of courses available, keyed by course.org (ie university). @@ -41,6 +43,7 @@ def get_courses(): # courseware = [ {'chapter_name':c.display_name, 'sections':[s.display_name for s in c.get_children()]} for c in chapters] # return courseware + def get_courseware_with_tabs(course_id): """ Given a course_id (string), return a courseware array of dictionaries for the @@ -101,18 +104,19 @@ def get_courseware_with_tabs(course_id): """ course = get_course_by_id(course_id) - chapters = [ chapter for chapter in course.get_children() if chapter.metadata.get('hide_from_toc','false').lower() != 'true' ] - courseware = [{'chapter_name':c.display_name, - 'sections':[{'section_name':s.display_name, - 'clickable_tab_count':len(s.get_children()) if (type(s)==seq_module.SequenceDescriptor) else 0, - 'tabs':[{'children_count':len(t.get_children()) if (type(t)==vertical_module.VerticalDescriptor) else 0, - 'class':t.__class__.__name__ } - for t in s.get_children() ]} + chapters = [chapter for chapter in course.get_children() if chapter.metadata.get('hide_from_toc', 'false').lower() != 'true'] + courseware = [{'chapter_name': c.display_name, + 'sections': [{'section_name': s.display_name, + 'clickable_tab_count': len(s.get_children()) if (type(s) == seq_module.SequenceDescriptor) else 0, + 'tabs': [{'children_count': len(t.get_children()) if (type(t) == vertical_module.VerticalDescriptor) else 0, + 'class': t.__class__.__name__} + for t in s.get_children()]} for s in c.get_children() if s.metadata.get('hide_from_toc', 'false').lower() != 'true']} - for c in chapters ] + for c in chapters] return courseware + def process_section(element, num_tabs=0): ''' Process section reads through whatever is in 'course-content' and classifies it according to sequence module type. diff --git a/lms/djangoapps/courseware/features/courseware.py b/lms/djangoapps/courseware/features/courseware.py index 05ecd63f4b..7e99cc9f55 100644 --- a/lms/djangoapps/courseware/features/courseware.py +++ b/lms/djangoapps/courseware/features/courseware.py @@ -1,7 +1,8 @@ from lettuce import world, step from lettuce.django import django_url + @step('I visit the courseware URL$') def i_visit_the_course_info_url(step): url = django_url('/courses/MITx/6.002x/2012_Fall/courseware') - world.browser.visit(url) \ No newline at end of file + world.browser.visit(url) diff --git a/lms/djangoapps/courseware/features/courseware_common.py b/lms/djangoapps/courseware/features/courseware_common.py index 8850c88fef..5ee21da906 100644 --- a/lms/djangoapps/courseware/features/courseware_common.py +++ b/lms/djangoapps/courseware/features/courseware_common.py @@ -1,36 +1,43 @@ from lettuce import world, step from lettuce.django import django_url + @step('I click on View Courseware') def i_click_on_view_courseware(step): css = 'p.enter-course' world.browser.find_by_css(css).first.click() + @step('I click on the "([^"]*)" tab$') def i_click_on_the_tab(step, tab): world.browser.find_link_by_text(tab).first.click() world.save_the_html() + @step('I visit the courseware URL$') def i_visit_the_course_info_url(step): url = django_url('/courses/MITx/6.002x/2012_Fall/courseware') world.browser.visit(url) + @step(u'I do not see "([^"]*)" anywhere on the page') def i_do_not_see_text_anywhere_on_the_page(step, text): - assert world.browser.is_text_not_present(text) + assert world.browser.is_text_not_present(text) + @step(u'I am on the dashboard page$') def i_am_on_the_dashboard_page(step): assert world.browser.is_element_present_by_css('section.courses') assert world.browser.url == django_url('/dashboard') + @step('the "([^"]*)" tab is active$') def the_tab_is_active(step, tab): css = '.course-tabs a.active' active_tab = world.browser.find_by_css(css) assert (active_tab.text == tab) + @step('the login dialog is visible$') def login_dialog_visible(step): css = 'form#login_form.login_form' diff --git a/lms/djangoapps/courseware/features/openended.py b/lms/djangoapps/courseware/features/openended.py index d37f9a0fae..0725a051ff 100644 --- a/lms/djangoapps/courseware/features/openended.py +++ b/lms/djangoapps/courseware/features/openended.py @@ -4,29 +4,33 @@ from nose.tools import assert_equals, assert_in from logging import getLogger logger = getLogger(__name__) + @step('I navigate to an openended question$') def navigate_to_an_openended_question(step): world.register_by_course_id('MITx/3.091x/2012_Fall') - world.log_in('robot@edx.org','test') + world.log_in('robot@edx.org', 'test') problem = '/courses/MITx/3.091x/2012_Fall/courseware/Week_10/Polymer_Synthesis/' world.browser.visit(django_url(problem)) tab_css = 'ol#sequence-list > li > a[data-element="5"]' world.browser.find_by_css(tab_css).click() + @step('I navigate to an openended question as staff$') def navigate_to_an_openended_question_as_staff(step): world.register_by_course_id('MITx/3.091x/2012_Fall', True) - world.log_in('robot@edx.org','test') + world.log_in('robot@edx.org', 'test') problem = '/courses/MITx/3.091x/2012_Fall/courseware/Week_10/Polymer_Synthesis/' world.browser.visit(django_url(problem)) tab_css = 'ol#sequence-list > li > a[data-element="5"]' world.browser.find_by_css(tab_css).click() + @step(u'I enter the answer "([^"]*)"$') def enter_the_answer_text(step, text): textarea_css = 'textarea' world.browser.find_by_css(textarea_css).first.fill(text) + @step(u'I submit the answer "([^"]*)"$') def i_submit_the_answer_text(step, text): textarea_css = 'textarea' @@ -34,53 +38,62 @@ def i_submit_the_answer_text(step, text): check_css = 'input.check' world.browser.find_by_css(check_css).click() + @step('I click the link for full output$') def click_full_output_link(step): link_css = 'a.full' world.browser.find_by_css(link_css).first.click() + @step(u'I visit the staff grading page$') def i_visit_the_staff_grading_page(step): # course_u = '/courses/MITx/3.091x/2012_Fall' # sg_url = '%s/staff_grading' % course_u world.browser.click_link_by_text('Instructor') - world.browser.click_link_by_text('Staff grading') + world.browser.click_link_by_text('Staff grading') # world.browser.visit(django_url(sg_url)) + @step(u'I see the grader message "([^"]*)"$') def see_grader_message(step, msg): message_css = 'div.external-grader-message' grader_msg = world.browser.find_by_css(message_css).text assert_in(msg, grader_msg) + @step(u'I see the grader status "([^"]*)"$') def see_the_grader_status(step, status): status_css = 'div.grader-status' grader_status = world.browser.find_by_css(status_css).text assert_equals(status, grader_status) + @step('I see the red X$') def see_the_red_x(step): x_css = 'div.grader-status > span.incorrect' assert world.browser.find_by_css(x_css) + @step(u'I see the grader score "([^"]*)"$') def see_the_grader_score(step, score): score_css = 'div.result-output > p' score_text = world.browser.find_by_css(score_css).text assert_equals(score_text, 'Score: %s' % score) + @step('I see the link for full output$') def see_full_output_link(step): link_css = 'a.full' assert world.browser.find_by_css(link_css) + @step('I see the spelling grading message "([^"]*)"$') def see_spelling_msg(step, msg): spelling_css = 'div.spelling' - spelling_msg = world.browser.find_by_css(spelling_css).text + spelling_msg = world.browser.find_by_css(spelling_css).text assert_equals('Spelling: %s' % msg, spelling_msg) + @step(u'my answer is queued for instructor grading$') def answer_is_queued_for_instructor_grading(step): list_css = 'ul.problem-list > li > a' diff --git a/lms/djangoapps/courseware/features/smart-accordion.py b/lms/djangoapps/courseware/features/smart-accordion.py index 95d3396f57..7c4770d632 100644 --- a/lms/djangoapps/courseware/features/smart-accordion.py +++ b/lms/djangoapps/courseware/features/smart-accordion.py @@ -7,6 +7,7 @@ from courses import * from logging import getLogger logger = getLogger(__name__) + def check_for_errors(): e = world.browser.find_by_css('.outside-app') if len(e) > 0: @@ -14,6 +15,7 @@ def check_for_errors(): else: assert True + @step(u'I verify all the content of each course') def i_verify_all_the_content_of_each_course(step): all_possible_courses = get_courses() @@ -34,11 +36,11 @@ def i_verify_all_the_content_of_each_course(step): check_for_errors() # Get the course. E.g. 'MITx/6.002x/2012_Fall' - current_course = sub('/info','', sub('.*/courses/', '', world.browser.url)) - validate_course(current_course,ids) + current_course = sub('/info', '', sub('.*/courses/', '', world.browser.url)) + validate_course(current_course, ids) world.browser.find_link_by_text('Courseware').click() - assert world.browser.is_element_present_by_id('accordion',wait_time=2) + assert world.browser.is_element_present_by_id('accordion', wait_time=2) check_for_errors() browse_course(current_course) @@ -46,6 +48,7 @@ def i_verify_all_the_content_of_each_course(step): world.browser.find_by_css('.user-link').click() check_for_errors() + def browse_course(course_id): ## count chapters from xml and page and compare @@ -91,7 +94,7 @@ def browse_course(course_id): world.browser.find_by_css('#accordion > nav > div')[chapter_it].find_by_tag('li')[section_it].find_by_tag('a').click() ## sometimes the course-content takes a long time to load - assert world.browser.is_element_present_by_css('.course-content',wait_time=5) + assert world.browser.is_element_present_by_css('.course-content', wait_time=5) ## look for server error div check_for_errors() @@ -108,7 +111,7 @@ def browse_course(course_id): rendered_tabs = 0 num_rendered_tabs = 0 - msg = ('%d tabs expected, %d tabs found, %s - %d - %s' % + msg = ('%d tabs expected, %d tabs found, %s - %d - %s' % (num_tabs, num_rendered_tabs, course_id, section_it, sections[section_it]['section_name'])) #logger.debug(msg) @@ -132,7 +135,7 @@ def browse_course(course_id): tab_class = tabs[tab_it]['class'] if tab_children != 0: rendered_items = world.browser.find_by_css('div#seq_content > section > ol > li > section') - num_rendered_items = len(rendered_items) + num_rendered_items = len(rendered_items) msg = ('%d items expected, %d items found, %s - %d - %s - tab %d' % (tab_children, num_rendered_items, course_id, section_it, sections[section_it]['section_name'], tab_it)) #logger.debug(msg) diff --git a/lms/djangoapps/courseware/grades.py b/lms/djangoapps/courseware/grades.py index f532e6c530..62dd656fe1 100644 --- a/lms/djangoapps/courseware/grades.py +++ b/lms/djangoapps/courseware/grades.py @@ -18,15 +18,17 @@ from models import StudentModule 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() - stack.extend( next_module.get_display_items() ) + stack.extend(next_module.get_display_items()) yield next_module + def yield_dynamic_descriptor_descendents(descriptor, module_creator): """ This returns all of the descendants of a descriptor. If the descriptor @@ -39,15 +41,15 @@ def yield_dynamic_descriptor_descendents(descriptor, module_creator): return module.get_child_descriptors() else: return descriptor.get_children() - - + + stack = [descriptor] while len(stack) > 0: next_descriptor = stack.pop() - stack.extend( get_dynamic_descriptor_children(next_descriptor) ) + stack.extend(get_dynamic_descriptor_children(next_descriptor)) yield next_descriptor - + def yield_problems(request, course, student): """ @@ -88,6 +90,7 @@ def yield_problems(request, course, student): if isinstance(problem, CapaModule): yield problem + def answer_distributions(request, course): """ Given a course_descriptor, compute frequencies of answers for each problem: @@ -150,7 +153,7 @@ def grade(student, request, course, student_module_cache=None, keep_raw_scores=F section_name = section_descriptor.metadata.get('display_name') should_grade_section = False - # If we haven't seen a single problem in the section, we don't have to grade it at all! We can assume 0% + # If we haven't seen a single problem in the section, we don't have to grade it at all! We can assume 0% for moduledescriptor in section['xmoduledescriptors']: if student_module_cache.lookup( course.id, moduledescriptor.category, moduledescriptor.location.url()): @@ -159,20 +162,20 @@ def grade(student, request, course, student_module_cache=None, keep_raw_scores=F if should_grade_section: scores = [] - + def create_module(descriptor): # TODO: We need the request to pass into here. If we could forgo that, our arguments # would be simpler - return get_module(student, request, descriptor.location, + return get_module(student, request, descriptor.location, student_module_cache, course.id) - + for module_descriptor in yield_dynamic_descriptor_descendents(section_descriptor, create_module): - + (correct, total) = get_score(course.id, student, module_descriptor, create_module, student_module_cache) if correct is None and total is None: continue - if settings.GENERATE_PROFILE_SCORES: # for debugging! + if settings.GENERATE_PROFILE_SCORES: # for debugging! if total > 1: correct = random.randrange(max(total - 2, 1), total + 1) else: @@ -208,12 +211,13 @@ def grade(student, request, course, student_module_cache=None, keep_raw_scores=F letter_grade = grade_for_percentage(course.grade_cutoffs, grade_summary['percent']) grade_summary['grade'] = letter_grade - grade_summary['totaled_scores'] = totaled_scores # make this available, eg for instructor download & debugging + grade_summary['totaled_scores'] = totaled_scores # make this available, eg for instructor download & debugging if keep_raw_scores: grade_summary['raw_scores'] = raw_scores # way to get all RAW scores out to instructor # so grader can be double-checked return grade_summary + def grade_for_percentage(grade_cutoffs, percentage): """ Returns a letter grade as defined in grading_policy (e.g. 'A' 'B' 'C' for 6.002x) or None. @@ -225,7 +229,7 @@ def grade_for_percentage(grade_cutoffs, percentage): """ letter_grade = None - + # Possible grades, sorted in descending order of score descending_grades = sorted(grade_cutoffs, key=lambda x: grade_cutoffs[x], reverse=True) for possible_grade in descending_grades: @@ -255,13 +259,13 @@ def progress_summary(student, request, course, student_module_cache): course: A Descriptor containing the course to grade student_module_cache: A StudentModuleCache initialized with all instance_modules for the student - + If the student does not have access to load the course module, this function will return None. - + """ - - + + # TODO: We need the request to pass into here. If we could forgo that, our arguments # would be simpler course_module = get_module(student, request, @@ -270,30 +274,30 @@ def progress_summary(student, request, course, student_module_cache): if not course_module: # This student must not have access to the course. return None - + chapters = [] # Don't include chapters that aren't displayable (e.g. due to error) for chapter_module in course_module.get_display_items(): # Skip if the chapter is hidden - hidden = chapter_module.metadata.get('hide_from_toc','false') + hidden = chapter_module.metadata.get('hide_from_toc', 'false') if hidden.lower() == 'true': continue - + sections = [] for section_module in chapter_module.get_display_items(): # Skip if the section is hidden - hidden = section_module.metadata.get('hide_from_toc','false') + hidden = section_module.metadata.get('hide_from_toc', 'false') if hidden.lower() == 'true': continue - + # Same for sections graded = section_module.metadata.get('graded', False) scores = [] - + module_creator = section_module.system.get_module - + for module_descriptor in yield_dynamic_descriptor_descendents(section_module.descriptor, module_creator): - + course_id = course.id (correct, total) = get_score(course_id, student, module_descriptor, module_creator, student_module_cache) if correct is None and total is None: diff --git a/lms/djangoapps/courseware/management/commands/clean_xml.py b/lms/djangoapps/courseware/management/commands/clean_xml.py index 425dd156c1..1989361b85 100644 --- a/lms/djangoapps/courseware/management/commands/clean_xml.py +++ b/lms/djangoapps/courseware/management/commands/clean_xml.py @@ -12,6 +12,7 @@ from django.core.management.base import BaseCommand from xmodule.modulestore.xml import XMLModuleStore from xmodule.errortracker import make_error_tracker + def traverse_tree(course): '''Load every descriptor in course. Return bool success value.''' queue = [course] diff --git a/lms/djangoapps/courseware/management/commands/metadata_to_json.py b/lms/djangoapps/courseware/management/commands/metadata_to_json.py index 8ef0dee7b3..b80736f693 100644 --- a/lms/djangoapps/courseware/management/commands/metadata_to_json.py +++ b/lms/djangoapps/courseware/management/commands/metadata_to_json.py @@ -14,6 +14,7 @@ from django.core.management.base import BaseCommand from xmodule.modulestore.xml import XMLModuleStore from xmodule.x_module import policy_key + def import_course(course_dir, verbose=True): course_dir = path(course_dir) data_dir = course_dir.dirname() @@ -44,6 +45,7 @@ def import_course(course_dir, verbose=True): return course + def node_metadata(node): # make a copy to_export = ('format', 'display_name', @@ -55,6 +57,7 @@ def node_metadata(node): d = {k: orig[k] for k in to_export if k in orig} return d + def get_metadata(course): d = OrderedDict({}) queue = [course] diff --git a/lms/djangoapps/courseware/migrations/0005_auto__add_offlinecomputedgrade__add_unique_offlinecomputedgrade_user_c.py b/lms/djangoapps/courseware/migrations/0005_auto__add_offlinecomputedgrade__add_unique_offlinecomputedgrade_user_c.py index 674f97cec8..4c21d22937 100644 --- a/lms/djangoapps/courseware/migrations/0005_auto__add_offlinecomputedgrade__add_unique_offlinecomputedgrade_user_c.py +++ b/lms/djangoapps/courseware/migrations/0005_auto__add_offlinecomputedgrade__add_unique_offlinecomputedgrade_user_c.py @@ -114,4 +114,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['courseware'] \ No newline at end of file + complete_apps = ['courseware'] diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py index bd01318f63..93a4beaafd 100644 --- a/lms/djangoapps/courseware/models.py +++ b/lms/djangoapps/courseware/models.py @@ -113,7 +113,7 @@ class StudentModuleCache(object): descriptor_filter=lambda descriptor: True, select_for_update=False): """ - obtain and return cache for descriptor descendents (ie children) AND modules required by the descriptor, + obtain and return cache for descriptor descendents (ie children) AND modules required by the descriptor, but which are not children of the module course_id: the course in the context of which we want StudentModules. @@ -212,7 +212,7 @@ class OfflineComputedGradeLog(models.Model): course_id = models.CharField(max_length=255, db_index=True) created = models.DateTimeField(auto_now_add=True, null=True, db_index=True) - seconds = models.IntegerField(default=0) # seconds elapsed for computation + seconds = models.IntegerField(default=0) # seconds elapsed for computation nstudents = models.IntegerField(default=0) def __unicode__(self): diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index b19796b357..ded84a971e 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -91,7 +91,7 @@ def toc_for_course(user, request, course, active_chapter, active_section): chapters = list() for chapter in course_module.get_display_items(): - hide_from_toc = chapter.metadata.get('hide_from_toc','false').lower() == 'true' + hide_from_toc = chapter.metadata.get('hide_from_toc', 'false').lower() == 'true' if hide_from_toc: continue @@ -166,6 +166,7 @@ def get_module_for_descriptor(user, request, descriptor, student_module_cache, c return _get_module(user, request, descriptor, student_module_cache, course_id, position=position, wrap_xmodule_display=wrap_xmodule_display) + def _get_module(user, request, descriptor, student_module_cache, course_id, position=None, wrap_xmodule_display=True): """ @@ -287,7 +288,7 @@ def _get_module(user, request, descriptor, student_module_cache, course_id, module.get_html = replace_static_urls( _get_html, module.metadata.get('data_dir', ''), - course_namespace = module.location._replace(category=None, name=None)) + course_namespace=module.location._replace(category=None, name=None)) # Allow URLs of the form '/course/' refer to the root of multicourse directory # hierarchy of this course @@ -300,6 +301,8 @@ def _get_module(user, request, descriptor, student_module_cache, course_id, return module # TODO (vshnayder): Rename this? It's very confusing. + + def get_instance_module(course_id, user, module, student_module_cache): """ Returns the StudentModule specific to this module for this student, @@ -329,6 +332,7 @@ def get_instance_module(course_id, user, module, student_module_cache): else: return None + 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 @@ -359,6 +363,7 @@ def get_shared_instance_module(course_id, user, module, student_module_cache): else: return None + @csrf_exempt def xqueue_callback(request, course_id, userid, id, dispatch): ''' @@ -415,8 +420,8 @@ def xqueue_callback(request, course_id, userid, id, dispatch): instance_module.save() #Bin score into range and increment stats - score_bucket=get_score_bucket(instance_module.grade, instance_module.max_grade) - org, course_num, run=course_id.split("/") + score_bucket = get_score_bucket(instance_module.grade, instance_module.max_grade) + org, course_num, run = course_id.split("/") statsd.increment("lms.courseware.question_answered", tags=["org:{0}".format(org), "course:{0}".format(course_num), @@ -456,9 +461,9 @@ def modx_dispatch(request, dispatch, location, course_id): return HttpResponse(json.dumps({'success': too_many_files_msg})) for inputfile in inputfiles: - if inputfile.size > settings.STUDENT_FILEUPLOAD_MAX_SIZE: # Bytes + 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)' %\ - (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE/(1000**2)) + (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE / (1000 ** 2)) return HttpResponse(json.dumps({'success': file_too_big_msg})) p[fileinput_id] = inputfiles @@ -499,7 +504,7 @@ def modx_dispatch(request, dispatch, location, course_id): # Don't track state for anonymous users (who don't have student modules) if instance_module is not None: instance_module.state = instance.get_instance_state() - instance_module.max_grade=instance.max_score() + instance_module.max_grade = instance.max_score() if instance.get_score(): instance_module.grade = instance.get_score()['score'] if (instance_module.grade != oldgrade or @@ -508,8 +513,8 @@ def modx_dispatch(request, dispatch, location, course_id): instance_module.save() #Bin score into range and increment stats - score_bucket=get_score_bucket(instance_module.grade, instance_module.max_grade) - org, course_num, run=course_id.split("/") + score_bucket = get_score_bucket(instance_module.grade, instance_module.max_grade) + org, course_num, run = course_id.split("/") statsd.increment("lms.courseware.question_answered", tags=["org:{0}".format(org), "course:{0}".format(course_num), @@ -526,6 +531,7 @@ def modx_dispatch(request, dispatch, location, course_id): # Return whatever the module wanted to return to the client/caller return HttpResponse(ajax_return) + def preview_chemcalc(request): """ Render an html preview of a chemical formula or equation. The fact that @@ -544,7 +550,7 @@ def preview_chemcalc(request): raise Http404 result = {'preview': '', - 'error': '' } + 'error': ''} formula = request.GET.get('formula') if formula is None: result['error'] = "No formula specified." @@ -563,17 +569,15 @@ def preview_chemcalc(request): return HttpResponse(json.dumps(result)) -def get_score_bucket(grade,max_grade): +def get_score_bucket(grade, max_grade): """ Function to split arbitrary score ranges into 3 buckets. Used with statsd tracking. """ - score_bucket="incorrect" - if(grade>0 and grade 0 and grade < max_grade): + score_bucket = "partial" + elif(grade == max_grade): + score_bucket = "correct" return score_bucket - - diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 4f5a881d97..a47141b183 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -33,6 +33,7 @@ from open_ended_grading import open_ended_notifications log = logging.getLogger(__name__) + class InvalidTabsException(Exception): """ A complaint about invalid tabs. @@ -41,6 +42,7 @@ class InvalidTabsException(Exception): CourseTabBase = namedtuple('CourseTab', 'name link is_active has_img img') + def CourseTab(name, link, is_active, has_img=False, img=""): return CourseTabBase(name, link, is_active, has_img, img) @@ -64,22 +66,26 @@ def _courseware(tab, user, course, active_page): link = reverse('courseware', args=[course.id]) return [CourseTab('Courseware', link, active_page == "courseware")] + def _course_info(tab, user, course, active_page): link = reverse('info', args=[course.id]) return [CourseTab(tab['name'], link, active_page == "info")] + def _progress(tab, user, course, active_page): if user.is_authenticated(): link = reverse('progress', args=[course.id]) return [CourseTab(tab['name'], link, active_page == "progress")] return [] + def _wiki(tab, user, course, active_page): if settings.WIKI_ENABLED: link = reverse('course_wiki', args=[course.id]) return [CourseTab(tab['name'], link, active_page == 'wiki')] return [] + def _discussion(tab, user, course, active_page): """ This tab format only supports the new Berkeley discussion forums. @@ -87,17 +93,19 @@ def _discussion(tab, user, course, active_page): if settings.MITX_FEATURES.get('ENABLE_DISCUSSION_SERVICE'): link = reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id]) - return [CourseTab(tab['name'], link, active_page=='discussion')] + return [CourseTab(tab['name'], link, active_page == 'discussion')] return [] + def _external_link(tab, user, course, active_page): # external links are never active return [CourseTab(tab['name'], tab['link'], False)] + def _static_tab(tab, user, course, active_page): link = reverse('static_tab', args=[course.id, tab['url_slug']]) active_str = 'static_tab_{0}'.format(tab['url_slug']) - return [CourseTab(tab['name'], link, active_page==active_str)] + return [CourseTab(tab['name'], link, active_page == active_str)] def _textbooks(tab, user, course, active_page): @@ -107,7 +115,7 @@ def _textbooks(tab, user, course, active_page): if user.is_authenticated() and settings.MITX_FEATURES.get('ENABLE_TEXTBOOK'): # since there can be more than one textbook, active_page is e.g. "book/0". return [CourseTab(textbook.title, reverse('book', args=[course.id, index]), - active_page=="textbook/{0}".format(index)) + active_page == "textbook/{0}".format(index)) for index, textbook in enumerate(course.textbooks)] return [] @@ -126,6 +134,7 @@ def _staff_grading(tab, user, course, active_page): return tab return [] + def _peer_grading(tab, user, course, active_page): if user.is_authenticated(): @@ -140,6 +149,7 @@ def _peer_grading(tab, user, course, active_page): return tab return [] + def _combined_open_ended_grading(tab, user, course, active_page): if user.is_authenticated(): link = reverse('open_ended_notifications', args=[course.id]) @@ -171,6 +181,7 @@ def key_checker(expected_keys): need_name = key_checker(['name']) + def null_validator(d): """ Don't check anything--use for tabs that don't need any params. (e.g. textbook) @@ -235,7 +246,7 @@ def get_course_tabs(user, course, active_page): """ Return the tabs to show a particular user, as a list of CourseTab items. """ - if not hasattr(course,'tabs') or not course.tabs: + if not hasattr(course, 'tabs') or not course.tabs: return get_default_tabs(user, course, active_page) # TODO (vshnayder): There needs to be a place to call this right after course @@ -269,7 +280,7 @@ def get_default_tabs(user, course, active_page): if hasattr(course, 'syllabus_present') and course.syllabus_present: link = reverse('syllabus', args=[course.id]) - tabs.append(CourseTab('Syllabus', link, active_page=='syllabus')) + tabs.append(CourseTab('Syllabus', link, active_page == 'syllabus')) tabs.extend(_textbooks({}, user, course, active_page)) @@ -290,10 +301,11 @@ def get_default_tabs(user, course, active_page): if has_access(user, course, 'staff'): link = reverse('instructor_dashboard', args=[course.id]) - tabs.append(CourseTab('Instructor', link, active_page=='instructor')) + tabs.append(CourseTab('Instructor', link, active_page == 'instructor')) return tabs + def get_static_tab_by_slug(course, tab_slug): """ Look for a tab with type 'static_tab' and the specified 'tab_slug'. Returns @@ -308,6 +320,7 @@ def get_static_tab_by_slug(course, tab_slug): return None + def get_static_tab_contents(request, cache, course, tab): loc = Location(course.location.tag, course.location.org, course.location.course, 'static_tab', tab['url_slug']) diff --git a/lms/djangoapps/courseware/tests/__init__.py b/lms/djangoapps/courseware/tests/__init__.py index 8b13789179..e69de29bb2 100644 --- a/lms/djangoapps/courseware/tests/__init__.py +++ b/lms/djangoapps/courseware/tests/__init__.py @@ -1 +0,0 @@ - diff --git a/lms/djangoapps/courseware/tests/factories.py b/lms/djangoapps/courseware/tests/factories.py index 6950e28565..a84b2b8475 100644 --- a/lms/djangoapps/courseware/tests/factories.py +++ b/lms/djangoapps/courseware/tests/factories.py @@ -5,6 +5,7 @@ from django.contrib.auth.models import Group from datetime import datetime import uuid + class UserProfileFactory(factory.Factory): FACTORY_FOR = UserProfile @@ -12,12 +13,14 @@ class UserProfileFactory(factory.Factory): name = 'Robot Studio' courseware = 'course.xml' + class RegistrationFactory(factory.Factory): FACTORY_FOR = Registration user = None activation_key = uuid.uuid4().hex + class UserFactory(factory.Factory): FACTORY_FOR = User @@ -32,11 +35,13 @@ class UserFactory(factory.Factory): last_login = datetime.now() date_joined = datetime.now() + class GroupFactory(factory.Factory): FACTORY_FOR = Group name = 'test_group' + class CourseEnrollmentAllowedFactory(factory.Factory): FACTORY_FOR = CourseEnrollmentAllowed diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py index ed9335d382..c0b28e7803 100644 --- a/lms/djangoapps/courseware/tests/test_access.py +++ b/lms/djangoapps/courseware/tests/test_access.py @@ -3,10 +3,11 @@ import time from mock import Mock from django.test import TestCase -from xmodule.modulestore import Location +from xmodule.modulestore import Location from factories import CourseEnrollmentAllowedFactory import courseware.access as access + class AccessTestCase(TestCase): def test__has_global_staff_access(self): u = Mock(is_staff=False) @@ -44,13 +45,13 @@ class AccessTestCase(TestCase): self.assertTrue(access._has_access_to_location(u, location, 'instructor', None)) - # A user does not have staff access if they are + # A user does not have staff access if they are # not in either the staff or the the instructor group g.name = 'student_only' self.assertFalse(access._has_access_to_location(u, location, 'staff', None)) - # A user does not have instructor access if they are + # A user does not have instructor access if they are # not in the instructor group g.name = 'student_only' self.assertFalse(access._has_access_to_location(u, location, @@ -69,7 +70,7 @@ class AccessTestCase(TestCase): # TODO: override DISABLE_START_DATES and test the start date branch of the method u = Mock() d = Mock() - d.start = time.gmtime(time.time() - 86400) # make sure the start time is in the past + d.start = time.gmtime(time.time() - 86400) # make sure the start time is in the past # Always returns true because DISABLE_START_DATES is set in test.py self.assertTrue(access._has_access_descriptor(u, d, 'load')) @@ -105,5 +106,5 @@ class AccessTestCase(TestCase): c.metadata.get = 'is_public' self.assertTrue(access._has_access_course_desc(u, c, 'enroll')) - # TODO: + # TODO: # Non-staff cannot enroll outside the open enrollment period if not specifically allowed diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 6c41cbac14..efa5ad823e 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -31,6 +31,7 @@ from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml import XMLModuleStore from xmodule.timeparse import stringify_time + def parse_json(response): """Parse response, which is assumed to be json""" return json.loads(response.content) @@ -49,6 +50,7 @@ def registration(email): # jump_to works with the xmlmodulestore or we have an even better solution # NOTE: this means this test requires mongo to be running. + def mongo_store_config(data_dir): return { 'default': { @@ -64,6 +66,7 @@ def mongo_store_config(data_dir): } } + def draft_mongo_store_config(data_dir): return { 'default': { @@ -79,6 +82,7 @@ def draft_mongo_store_config(data_dir): } } + def xml_store_config(data_dir): return { 'default': { @@ -95,6 +99,7 @@ TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR) TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR) + class ActivateLoginTestCase(TestCase): '''Check that we can activate and log in''' @@ -286,13 +291,13 @@ class PageLoader(ActivateLoginTestCase): all_ok = False num_bad += 1 elif descriptor.location.category == 'static_tab': - resp = self.client.get(reverse('static_tab', kwargs={'course_id': course_id, 'tab_slug' : descriptor.location.name})) + resp = self.client.get(reverse('static_tab', kwargs={'course_id': course_id, 'tab_slug': descriptor.location.name})) msg = str(resp.status_code) if resp.status_code != 200: msg = "ERROR " + msg all_ok = False - num_bad += 1 + num_bad += 1 elif descriptor.location.category == 'course_info': resp = self.client.get(reverse('info', kwargs={'course_id': course_id})) msg = str(resp.status_code) @@ -300,7 +305,7 @@ class PageLoader(ActivateLoginTestCase): if resp.status_code != 200: msg = "ERROR " + msg all_ok = False - num_bad += 1 + num_bad += 1 elif descriptor.location.category == 'custom_tag_template': pass else: @@ -321,7 +326,7 @@ class PageLoader(ActivateLoginTestCase): # check content to make sure there were no rendering failures content = resp.content - if content.find("this module is temporarily unavailable")>=0: + if content.find("this module is temporarily unavailable") >= 0: msg = "ERROR unavailable module " all_ok = False num_bad += 1 @@ -335,7 +340,7 @@ class PageLoader(ActivateLoginTestCase): self.assertTrue(all_ok) # fail fast print "{0}/{1} good".format(n - num_bad, n) - log.info( "{0}/{1} good".format(n - num_bad, n)) + log.info("{0}/{1} good".format(n - num_bad, n)) self.assertTrue(all_ok) @@ -347,7 +352,7 @@ class TestCoursesLoadTestCase_XmlModulestore(PageLoader): def setUp(self): ActivateLoginTestCase.setUp(self) xmodule.modulestore.django._MODULESTORES = {} - + def test_toy_course_loads(self): module_store = XMLModuleStore( TEST_DATA_DIR, @@ -376,7 +381,7 @@ class TestCoursesLoadTestCase_MongoModulestore(PageLoader): ActivateLoginTestCase.setUp(self) xmodule.modulestore.django._MODULESTORES = {} modulestore().collection.drop() - + def test_toy_course_loads(self): module_store = modulestore() import_from_xml(module_store, TEST_DATA_DIR, ['toy']) @@ -431,7 +436,7 @@ class TestNavigation(PageLoader): # Now we directly navigate to a section in a different chapter self.check_for_get_code(200, reverse('courseware_section', kwargs={'course_id': self.toy.id, - 'chapter':'secret:magic', 'section':'toyvideo'})) + 'chapter': 'secret:magic', 'section': 'toyvideo'})) # And now hitting the courseware tab should redirect to 'secret:magic' resp = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id})) @@ -565,7 +570,7 @@ class TestViewAuth(PageLoader): """Actually do the test, relying on settings to be right.""" # Make courses start in the future - tomorrow = time.time() + 24*3600 + tomorrow = time.time() + 24 * 3600 self.toy.metadata['start'] = stringify_time(time.gmtime(tomorrow)) self.full.metadata['start'] = stringify_time(time.gmtime(tomorrow)) @@ -603,7 +608,7 @@ class TestViewAuth(PageLoader): def instructor_urls(course): """list of urls that only instructors/staff should be able to see""" - urls = reverse_urls(['instructor_dashboard','gradebook','grade_summary'], + urls = reverse_urls(['instructor_dashboard', 'gradebook', 'grade_summary'], course) return urls @@ -770,7 +775,7 @@ class TestCourseGrader(PageLoader): def find_course(course_id): """Assumes the course is present""" - return [c for c in courses if c.id==course_id][0] + return [c for c in courses if c.id == course_id][0] self.graded_course = find_course("edX/graded/2012_Fall") @@ -825,17 +830,17 @@ class TestCourseGrader(PageLoader): modx_url = reverse('modx_dispatch', kwargs={ - 'course_id' : self.graded_course.id, - 'location' : problem_location, - 'dispatch' : 'problem_check', } + 'course_id': self.graded_course.id, + 'location': problem_location, + 'dispatch': 'problem_check', } ) resp = self.client.post(modx_url, { 'input_i4x-edX-graded-problem-{0}_2_1'.format(problem_url_name): responses[0], 'input_i4x-edX-graded-problem-{0}_2_2'.format(problem_url_name): responses[1], }) - print "modx_url" , modx_url, "responses" , responses - print "resp" , resp + print "modx_url", modx_url, "responses", responses + print "resp", resp return resp @@ -847,9 +852,9 @@ class TestCourseGrader(PageLoader): modx_url = reverse('modx_dispatch', kwargs={ - 'course_id' : self.graded_course.id, - 'location' : problem_location, - 'dispatch' : 'problem_reset', } + 'course_id': self.graded_course.id, + 'location': problem_location, + 'dispatch': 'problem_reset', } ) resp = self.client.post(modx_url) @@ -873,7 +878,7 @@ class TestCourseGrader(PageLoader): # Only get half of the first problem correct self.submit_question_answer('H1P1', ['Correct', 'Incorrect']) self.check_grade_percent(0.06) - self.assertEqual(earned_hw_scores(), [1.0, 0, 0]) # Order matters + self.assertEqual(earned_hw_scores(), [1.0, 0, 0]) # Order matters self.assertEqual(score_for_hw('Homework1'), [1.0, 0.0]) # Get both parts of the first problem correct @@ -905,14 +910,13 @@ class TestCourseGrader(PageLoader): # Third homework self.submit_question_answer('H3P1', ['Correct', 'Correct']) - self.check_grade_percent(0.42) # Score didn't change + self.check_grade_percent(0.42) # Score didn't change self.assertEqual(earned_hw_scores(), [4.0, 4.0, 2.0]) self.submit_question_answer('H3P2', ['Correct', 'Correct']) - self.check_grade_percent(0.5) # Now homework2 dropped. Score changes + self.check_grade_percent(0.5) # Now homework2 dropped. Score changes self.assertEqual(earned_hw_scores(), [4.0, 4.0, 4.0]) # Now we answer the final question (worth half of the grade) self.submit_question_answer('FinalQuestion', ['Correct', 'Correct']) - self.check_grade_percent(1.0) # Hooray! We got 100% - + self.check_grade_percent(1.0) # Hooray! We got 100% diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 5d65d7c632..02a4b5f5f2 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -137,6 +137,7 @@ def redirect_to_course_position(course_module, first_time): 'chapter': chapter.url_name, 'section': section.url_name})) + def save_child_position(seq_module, child_name, instance_module): """ child_name: url_name of the child @@ -152,6 +153,7 @@ def save_child_position(seq_module, child_name, instance_module): instance_module.state = seq_module.get_instance_state() instance_module.save() + @login_required @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @@ -184,7 +186,7 @@ def index(request, course_id, chapter=None, section=None, registered = registered_for_course(course, request.user) if not registered: # TODO (vshnayder): do course instructors need to be registered to see course? - log.debug('User %s tried to view course %s but is not enrolled' % (request.user,course.location.url())) + log.debug('User %s tried to view course %s but is not enrolled' % (request.user, course.location.url())) return redirect(reverse('about_course', args=[course.id])) try: @@ -212,7 +214,7 @@ def index(request, course_id, chapter=None, section=None, 'init': '', 'content': '', 'staff_access': staff_access, - 'xqa_server': settings.MITX_FEATURES.get('USE_XQA_SERVER','http://xqa:server@content-qa.mitx.mit.edu/xqa') + 'xqa_server': settings.MITX_FEATURES.get('USE_XQA_SERVER', 'http://xqa:server@content-qa.mitx.mit.edu/xqa') } chapter_descriptor = course.get_child_by(lambda m: m.url_name == chapter) @@ -288,7 +290,7 @@ def index(request, course_id, chapter=None, section=None, try: result = render_to_response('courseware/courseware-error.html', {'staff_access': staff_access, - 'course' : course}) + 'course': course}) except: # Let the exception propagate, relying on global config to at # at least return a nice error message @@ -297,6 +299,7 @@ def index(request, course_id, chapter=None, section=None, return result + @ensure_csrf_cookie def jump_to(request, course_id, location): ''' @@ -333,6 +336,7 @@ def jump_to(request, course_id, location): else: return redirect('courseware_position', course_id=course_id, chapter=chapter, section=section, position=position) + @ensure_csrf_cookie def course_info(request, course_id): """ @@ -343,9 +347,10 @@ 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('courseware/info.html', {'request' : request, 'course_id' : course_id, 'cache' : None, + return render_to_response('courseware/info.html', {'request': request, 'course_id': course_id, 'cache': None, 'course': course, 'staff_access': staff_access}) + @ensure_csrf_cookie def static_tab(request, course_id, tab_slug): """ @@ -368,9 +373,11 @@ def static_tab(request, course_id, tab_slug): {'course': course, 'tab': tab, 'tab_contents': contents, - 'staff_access': staff_access,}) + 'staff_access': staff_access, }) # TODO arjun: remove when custom tabs in place, see courseware/syllabus.py + + @ensure_csrf_cookie def syllabus(request, course_id): """ @@ -382,7 +389,7 @@ def syllabus(request, course_id): staff_access = has_access(request.user, course, 'staff') return render_to_response('courseware/syllabus.html', {'course': course, - 'staff_access': staff_access,}) + 'staff_access': staff_access, }) def registered_for_course(course, user): @@ -394,6 +401,7 @@ def registered_for_course(course, user): else: return False + @ensure_csrf_cookie @cache_if_anonymous def course_about(request, course_id): @@ -412,7 +420,7 @@ def course_about(request, course_id): {'course': course, 'registered': registered, 'course_target': course_target, - 'show_courseware_link' : show_courseware_link}) + 'show_courseware_link': show_courseware_link}) @ensure_csrf_cookie @@ -425,6 +433,7 @@ def static_university_profile(request, org_id): context = dict(courses=[], org_id=org_id) return render_to_response(template_file, context) + @ensure_csrf_cookie @cache_if_anonymous def university_profile(request, org_id): @@ -446,6 +455,7 @@ def university_profile(request, org_id): return render_to_response(template_file, context) + def render_notifications(request, course, notifications): context = { 'notifications': notifications, @@ -454,6 +464,7 @@ def render_notifications(request, course, notifications): } return render_to_string('courseware/notifications.html', context) + @login_required def news(request, course_id): course = get_course_with_access(request.user, course_id, 'load') @@ -467,6 +478,7 @@ def news(request, course_id): return render_to_response('courseware/news.html', context) + @login_required @cache_control(no_cache=True, no_store=True, must_revalidate=True) def progress(request, course_id, student_id=None): diff --git a/lms/djangoapps/dashboard/views.py b/lms/djangoapps/dashboard/views.py index 964b3fac4a..f5929f241b 100644 --- a/lms/djangoapps/dashboard/views.py +++ b/lms/djangoapps/dashboard/views.py @@ -3,6 +3,7 @@ import json from datetime import datetime from django.http import HttpResponse, Http404 + def dictfetchall(cursor): '''Returns all rows from a cursor as a dict. Borrowed from Django documentation''' @@ -12,23 +13,24 @@ def dictfetchall(cursor): for row in cursor.fetchall() ] + def dashboard(request): """ Quick hack to show staff enrollment numbers. This should be replaced with a real dashboard later. This version is a short-term - bandaid for the next couple weeks. + bandaid for the next couple weeks. """ if not request.user.is_staff: raise Http404 - queries=[] + 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() - results =[] + results = [] for query in queries: cursor.execute(query) diff --git a/lms/djangoapps/django_comment_client/base/urls.py b/lms/djangoapps/django_comment_client/base/urls.py index 23f2afa037..d8fd4927fb 100644 --- a/lms/djangoapps/django_comment_client/base/urls.py +++ b/lms/djangoapps/django_comment_client/base/urls.py @@ -23,7 +23,7 @@ urlpatterns = patterns('django_comment_client.base.views', url(r'comments/(?P[\w\-]+)/upvote$', 'vote_for_comment', {'value': 'up'}, name='upvote_comment'), url(r'comments/(?P[\w\-]+)/downvote$', 'vote_for_comment', {'value': 'down'}, name='downvote_comment'), url(r'comments/(?P[\w\-]+)/unvote$', 'undo_vote_for_comment', name='undo_vote_for_comment'), - + url(r'^(?P[\w\-.]+)/threads/create$', 'create_thread', name='create_thread'), # TODO should we search within the board? url(r'^(?P[\w\-.]+)/threads/search_similar$', 'search_similar_threads', name='search_similar_threads'), diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index 777c7bafce..7ca00cb37c 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -30,6 +30,7 @@ from django_comment_client.models import Role log = logging.getLogger(__name__) + def permitted(fn): @functools.wraps(fn) def wrapper(request, *args, **kwargs): @@ -47,6 +48,7 @@ def permitted(fn): return JsonError("unauthorized", status=401) return wrapper + def ajax_content_response(request, course_id, content, template_name): context = { 'course_id': course_id, @@ -82,11 +84,11 @@ def create_thread(request, course_id, commentable_id): thread = cc.Thread(**extract(post, ['body', 'title', 'tags'])) thread.update_attributes(**{ - 'anonymous' : anonymous, - 'anonymous_to_peers' : anonymous_to_peers, - 'commentable_id' : commentable_id, - 'course_id' : course_id, - 'user_id' : request.user.id, + 'anonymous': anonymous, + 'anonymous_to_peers': anonymous_to_peers, + 'commentable_id': commentable_id, + 'course_id': course_id, + 'user_id': request.user.id, }) @@ -118,6 +120,7 @@ def create_thread(request, course_id, commentable_id): else: return JsonResponse(utils.safe_content(data)) + @require_POST @login_required @permitted @@ -130,6 +133,7 @@ def update_thread(request, course_id, thread_id): else: return JsonResponse(utils.safe_content(thread.to_dict())) + def _create_comment(request, course_id, thread_id=None, parent_id=None): post = request.POST comment = cc.Comment(**extract(post, ['body'])) @@ -146,12 +150,12 @@ def _create_comment(request, course_id, thread_id=None, parent_id=None): anonymous_to_peers = False comment.update_attributes(**{ - 'anonymous' : anonymous, - 'anonymous_to_peers' : anonymous_to_peers, - 'user_id' : request.user.id, - 'course_id' : course_id, - 'thread_id' : thread_id, - 'parent_id' : parent_id, + 'anonymous': anonymous, + 'anonymous_to_peers': anonymous_to_peers, + 'user_id': request.user.id, + 'course_id': course_id, + 'thread_id': thread_id, + 'parent_id': parent_id, }) comment.save() if post.get('auto_subscribe', 'false').lower() == 'true': @@ -162,6 +166,7 @@ def _create_comment(request, course_id, thread_id=None, parent_id=None): else: return JsonResponse(utils.safe_content(comment.to_dict())) + @require_POST @login_required @permitted @@ -171,6 +176,7 @@ def create_comment(request, course_id, thread_id): return JsonError("Comment level too deep") return _create_comment(request, course_id, thread_id=thread_id) + @require_POST @login_required @permitted @@ -179,6 +185,7 @@ def delete_thread(request, course_id, thread_id): thread.delete() return JsonResponse(utils.safe_content(thread.to_dict())) + @require_POST @login_required @permitted @@ -191,6 +198,7 @@ def update_comment(request, course_id, comment_id): else: return JsonResponse(utils.safe_content(comment.to_dict())) + @require_POST @login_required @permitted @@ -200,6 +208,7 @@ def endorse_comment(request, course_id, comment_id): comment.save() return JsonResponse(utils.safe_content(comment.to_dict())) + @require_POST @login_required @permitted @@ -213,6 +222,7 @@ def openclose_thread(request, course_id, thread_id): 'ability': utils.get_ability(course_id, thread, request.user), }) + @require_POST @login_required @permitted @@ -222,6 +232,7 @@ def create_sub_comment(request, course_id, comment_id): return JsonError("Comment level too deep") return _create_comment(request, course_id, parent_id=comment_id) + @require_POST @login_required @permitted @@ -230,6 +241,7 @@ def delete_comment(request, course_id, comment_id): comment.delete() return JsonResponse(utils.safe_content(comment.to_dict())) + @require_POST @login_required @permitted @@ -239,6 +251,7 @@ def vote_for_comment(request, course_id, comment_id, value): user.vote(comment, value) return JsonResponse(utils.safe_content(comment.to_dict())) + @require_POST @login_required @permitted @@ -248,6 +261,7 @@ def undo_vote_for_comment(request, course_id, comment_id): user.unvote(comment) return JsonResponse(utils.safe_content(comment.to_dict())) + @require_POST @login_required @permitted @@ -257,6 +271,7 @@ def vote_for_thread(request, course_id, thread_id, value): user.vote(thread, value) return JsonResponse(utils.safe_content(thread.to_dict())) + @require_POST @login_required @permitted @@ -276,6 +291,7 @@ def follow_thread(request, course_id, thread_id): user.follow(thread) return JsonResponse({}) + @require_POST @login_required @permitted @@ -285,6 +301,7 @@ def follow_commentable(request, course_id, commentable_id): user.follow(commentable) return JsonResponse({}) + @require_POST @login_required @permitted @@ -294,6 +311,7 @@ def follow_user(request, course_id, followed_user_id): user.follow(followed_user) return JsonResponse({}) + @require_POST @login_required @permitted @@ -303,6 +321,7 @@ def unfollow_thread(request, course_id, thread_id): user.unfollow(thread) return JsonResponse({}) + @require_POST @login_required @permitted @@ -312,6 +331,7 @@ def unfollow_commentable(request, course_id, commentable_id): user.unfollow(commentable) return JsonResponse({}) + @require_POST @login_required @permitted @@ -321,6 +341,7 @@ def unfollow_user(request, course_id, followed_user_id): user.unfollow(followed_user) return JsonResponse({}) + @require_POST @login_required @permitted @@ -351,6 +372,7 @@ def update_moderator_status(request, course_id, user_id): else: return JsonResponse({}) + @require_GET def search_similar_threads(request, course_id, commentable_id): text = request.GET.get('text', None) @@ -362,11 +384,12 @@ def search_similar_threads(request, course_id, commentable_id): threads = cc.search_similar_threads(course_id, recursive=False, query_params=query_params) else: theads = [] - context = { 'threads': map(utils.extend_content, threads) } + context = {'threads': map(utils.extend_content, threads)} return JsonResponse({ 'html': render_to_string('discussion/_similar_posts.html', context) }) + @require_GET def tags_autocomplete(request, course_id): value = request.GET.get('q', None) @@ -375,10 +398,11 @@ def tags_autocomplete(request, course_id): results = cc.tags_autocomplete(value) return JsonResponse(results) + @require_POST @login_required @csrf.csrf_exempt -def upload(request, course_id):#ajax upload file to a question or answer +def upload(request, course_id): # ajax upload file to a question or answer """view that handles file upload via Ajax """ @@ -409,7 +433,7 @@ def upload(request, course_id):#ajax upload file to a question or answer time.time() ).replace( '.', - str(random.randint(0,100000)) + str(random.randint(0, 100000)) ) + file_extension file_storage = get_storage_class()() diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index 2c1d3c68d5..70d9f40fcf 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -30,6 +30,7 @@ PAGES_NEARBY_DELTA = 2 escapedict = {'"': '"'} log = logging.getLogger("edx.discussions") + def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAGE): """ This may raise cc.utils.CommentClientError or @@ -78,6 +79,7 @@ def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAG return threads, query_params + def inline_discussion(request, course_id, discussion_id): """ Renders JSON for DiscussionModules @@ -111,6 +113,7 @@ def inline_discussion(request, course_id, discussion_id): 'allow_anonymous': allow_anonymous, }) + @login_required def forum_form_discussion(request, course_id): """ @@ -136,7 +139,7 @@ def forum_form_discussion(request, course_id): thread.update(courseware_context) if request.is_ajax(): return utils.JsonResponse({ - 'discussion_data': threads, # TODO: Standardize on 'discussion_data' vs 'threads' + 'discussion_data': threads, # TODO: Standardize on 'discussion_data' vs 'threads' 'annotated_content_info': annotated_content_info, 'num_pages': query_params['num_pages'], 'page': query_params['page'], @@ -157,11 +160,11 @@ def forum_form_discussion(request, course_id): 'course': course, #'recent_active_threads': recent_active_threads, #'trending_tags': trending_tags, - 'staff_access' : has_access(request.user, course, 'staff'), - 'threads': saxutils.escape(json.dumps(threads),escapedict), + 'staff_access': has_access(request.user, course, 'staff'), + 'threads': saxutils.escape(json.dumps(threads), escapedict), 'thread_pages': query_params['num_pages'], - 'user_info': saxutils.escape(json.dumps(user_info),escapedict), - 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info),escapedict), + 'user_info': saxutils.escape(json.dumps(user_info), escapedict), + 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), 'course_id': course.id, 'category_map': category_map, 'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict), @@ -169,6 +172,7 @@ def forum_form_discussion(request, course_id): # print "start rendering.." return render_to_response('discussion/index.html', context) + @login_required def single_thread(request, course_id, discussion_id, thread_id): @@ -234,13 +238,13 @@ def single_thread(request, course_id, discussion_id, thread_id): context = { 'discussion_id': discussion_id, 'csrf': csrf(request)['csrf_token'], - 'init': '', #TODO: What is this? - 'user_info': saxutils.escape(json.dumps(user_info),escapedict), + 'init': '', # TODO: What is this? + 'user_info': saxutils.escape(json.dumps(user_info), escapedict), 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), 'course': course, #'recent_active_threads': recent_active_threads, #'trending_tags': trending_tags, - 'course_id': course.id, #TODO: Why pass both course and course.id to template? + 'course_id': course.id, # TODO: Why pass both course and course.id to template? 'thread_id': thread_id, 'threads': saxutils.escape(json.dumps(threads), escapedict), 'category_map': category_map, @@ -250,6 +254,7 @@ def single_thread(request, course_id, discussion_id, thread_id): return render_to_response('discussion/single_thread.html', context) + @login_required def user_profile(request, course_id, user_id): #TODO: Allow sorting? @@ -259,7 +264,7 @@ def user_profile(request, course_id, user_id): query_params = { 'page': request.GET.get('page', 1), - 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities + 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities } threads, page, num_pages = profiled_user.active_threads(query_params) @@ -274,7 +279,7 @@ def user_profile(request, course_id, user_id): 'discussion_data': map(utils.safe_content, threads), 'page': query_params['page'], 'num_pages': query_params['num_pages'], - 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info),escapedict), + 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), }) else: @@ -285,8 +290,8 @@ def user_profile(request, course_id, user_id): 'django_user': User.objects.get(id=user_id), 'profiled_user': profiled_user.to_dict(), 'threads': saxutils.escape(json.dumps(threads), escapedict), - 'user_info': saxutils.escape(json.dumps(user_info),escapedict), - 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info),escapedict), + 'user_info': saxutils.escape(json.dumps(user_info), escapedict), + 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), # 'content': content, } @@ -302,7 +307,7 @@ def followed_threads(request, course_id, user_id): query_params = { 'page': request.GET.get('page', 1), - 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities + 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities 'sort_key': request.GET.get('sort_key', 'date'), 'sort_order': request.GET.get('sort_order', 'desc'), } @@ -328,8 +333,8 @@ def followed_threads(request, course_id, user_id): 'django_user': User.objects.get(id=user_id), 'profiled_user': profiled_user.to_dict(), 'threads': saxutils.escape(json.dumps(threads), escapedict), - 'user_info': saxutils.escape(json.dumps(user_info),escapedict), - 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info),escapedict), + 'user_info': saxutils.escape(json.dumps(user_info), escapedict), + 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), # 'content': content, } diff --git a/lms/djangoapps/django_comment_client/helpers.py b/lms/djangoapps/django_comment_client/helpers.py index 0a1e8639ef..733856e2a9 100644 --- a/lms/djangoapps/django_comment_client/helpers.py +++ b/lms/djangoapps/django_comment_client/helpers.py @@ -15,12 +15,16 @@ import os # This method is used to pluralize the words "discussion" and "comment" # when referring to how many discussion threads or comments the user # has contributed to. + + def pluralize(singular_term, count): if int(count) >= 2 or int(count) == 0: return singular_term + 's' return singular_term # TODO there should be a better way to handle this + + def include_mustache_templates(): mustache_dir = settings.PROJECT_ROOT / 'templates' / 'discussion' / 'mustache' valid_file_name = lambda file_name: file_name.endswith('.mustache') @@ -31,6 +35,7 @@ def include_mustache_templates(): file_contents = map(read_file, filter(valid_file_name, os.listdir(mustache_dir))) return '\n'.join(map(wrap_in_tag, map(strip_file_name, file_contents))) + def render_content(content, additional_context={}): context = { diff --git a/lms/djangoapps/django_comment_client/management/commands/assign_roles_for_course.py b/lms/djangoapps/django_comment_client/management/commands/assign_roles_for_course.py index 82f2290bc7..304907cdae 100644 --- a/lms/djangoapps/django_comment_client/management/commands/assign_roles_for_course.py +++ b/lms/djangoapps/django_comment_client/management/commands/assign_roles_for_course.py @@ -8,6 +8,7 @@ from django.core.management.base import BaseCommand, CommandError from student.models import CourseEnrollment, assign_default_role + class Command(BaseCommand): args = 'course_id' help = 'Add roles for all users in a course' diff --git a/lms/djangoapps/django_comment_client/management/commands/create_roles_for_existing.py b/lms/djangoapps/django_comment_client/management/commands/create_roles_for_existing.py index d1244a6690..638d59f5fe 100644 --- a/lms/djangoapps/django_comment_client/management/commands/create_roles_for_existing.py +++ b/lms/djangoapps/django_comment_client/management/commands/create_roles_for_existing.py @@ -8,6 +8,7 @@ from django.core.management.base import BaseCommand, CommandError from student.models import CourseEnrollment, assign_default_role + class Command(BaseCommand): args = 'course_id' help = 'Seed default permisssions and roles' diff --git a/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py b/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py index 958b67cdb3..6a31e73af3 100644 --- a/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py +++ b/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py @@ -18,7 +18,7 @@ class Command(BaseCommand): 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", + "update_comment", "create_sub_comment", "unvote", "create_thread", "follow_commentable", "unfollow_commentable", "create_comment", ]: student_role.add_permission(per) diff --git a/lms/djangoapps/django_comment_client/middleware.py b/lms/djangoapps/django_comment_client/middleware.py index 08e20b0296..abf2d40cab 100644 --- a/lms/djangoapps/django_comment_client/middleware.py +++ b/lms/djangoapps/django_comment_client/middleware.py @@ -2,7 +2,8 @@ from comment_client import CommentClientError from django_comment_client.utils import JsonError import json -class AjaxExceptionMiddleware(object): + +class AjaxExceptionMiddleware(object): def process_exception(self, request, exception): if isinstance(exception, CommentClientError) and request.is_ajax(): return JsonError(json.loads(exception.message)) diff --git a/lms/djangoapps/django_comment_client/migrations/0001_initial.py b/lms/djangoapps/django_comment_client/migrations/0001_initial.py index 4993984d74..0b5f88e2f2 100644 --- a/lms/djangoapps/django_comment_client/migrations/0001_initial.py +++ b/lms/djangoapps/django_comment_client/migrations/0001_initial.py @@ -129,4 +129,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['django_comment_client'] \ No newline at end of file + complete_apps = ['django_comment_client'] diff --git a/lms/djangoapps/django_comment_client/models.py b/lms/djangoapps/django_comment_client/models.py index 10c05c75e9..023b355a29 100644 --- a/lms/djangoapps/django_comment_client/models.py +++ b/lms/djangoapps/django_comment_client/models.py @@ -35,7 +35,7 @@ class Role(models.Model): def __unicode__(self): return self.name + " for " + (self.course_id if self.course_id else "all courses") - def inherit_permissions(self, role): # TODO the name of this method is a little bit confusing, + def inherit_permissions(self, role): # TODO the name of this method is a little bit confusing, # since it's one-off and doesn't handle inheritance later if role.course_id and role.course_id != self.course_id: logging.warning("{0} cannot inherit permissions from {1} due to course_id inconsistency", \ @@ -52,7 +52,7 @@ class Role(models.Model): (permission.startswith('edit') or permission.startswith('update') or permission.startswith('create')) and \ (not course.forum_posts_allowed): return False - + return self.permissions.filter(name=permission).exists() diff --git a/lms/djangoapps/django_comment_client/mustache_helpers.py b/lms/djangoapps/django_comment_client/mustache_helpers.py index 9756294696..5743dba9cb 100644 --- a/lms/djangoapps/django_comment_client/mustache_helpers.py +++ b/lms/djangoapps/django_comment_client/mustache_helpers.py @@ -7,6 +7,8 @@ import inspect # This method is used to pluralize the words "discussion" and "comment" # which is why you need to tack on an "s" for the case of 0 or two or more. + + def pluralize(content, text): num, word = text.split(' ') num = int(num or '0') @@ -15,12 +17,15 @@ def pluralize(content, text): else: return word + def url_for_user(content, user_id): return urlresolvers.reverse('django_comment_client.forum.views.user_profile', args=[content['course_id'], user_id]) -def url_for_tags(content, tags): # assume that attribute 'tags' is in the format u'a, b, c' + +def url_for_tags(content, tags): # assume that attribute 'tags' is in the format u'a, b, c' return _url_for_tags(content['course_id'], tags) + def close_thread_text(content): if content.get('closed'): return 'Re-open thread' diff --git a/lms/djangoapps/django_comment_client/permissions.py b/lms/djangoapps/django_comment_client/permissions.py index b95a890dda..dfdcd3e7ba 100644 --- a/lms/djangoapps/django_comment_client/permissions.py +++ b/lms/djangoapps/django_comment_client/permissions.py @@ -8,6 +8,7 @@ from util.cache import cache from django.core import cache cache = cache.get_cache('default') + def cached_has_permission(user, permission, course_id=None): """ Call has_permission if it's not cached. A change in a user's role or @@ -21,6 +22,7 @@ def cached_has_permission(user, permission, course_id=None): cache.set(key, val, CACHE_LIFESPAN) return val + def has_permission(user, permission, course_id=None): for role in user.roles.filter(course_id=course_id): if role.has_permission(permission): @@ -29,6 +31,8 @@ def has_permission(user, permission, course_id=None): CONDITIONS = ['is_open', 'is_author'] + + def check_condition(user, condition, course_id, data): def check_open(user, condition, course_id, data): try: @@ -43,8 +47,8 @@ def check_condition(user, condition, course_id, data): return False handlers = { - 'is_open' : check_open, - 'is_author' : check_author, + 'is_open': check_open, + 'is_author': check_author, } return handlers[condition](user, condition, course_id, data) @@ -93,7 +97,7 @@ VIEW_PERMISSIONS = { 'unfollow_commentable': ['unfollow_commentable'], 'unfollow_user' : ['unfollow_user'], 'create_thread' : ['create_thread'], - 'update_moderator_status' : ['manage_moderator'], + 'update_moderator_status': ['manage_moderator'], } diff --git a/lms/djangoapps/django_comment_client/settings.py b/lms/djangoapps/django_comment_client/settings.py index 3234c32478..b9a8d18081 100644 --- a/lms/djangoapps/django_comment_client/settings.py +++ b/lms/djangoapps/django_comment_client/settings.py @@ -1,7 +1,7 @@ from django.conf import settings MAX_COMMENT_DEPTH = None -MAX_UPLOAD_FILE_SIZE = 1024 * 1024 #result in bytes +MAX_UPLOAD_FILE_SIZE = 1024 * 1024 # result in bytes ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff') if hasattr(settings, 'DISCUSSION_SETTINGS'): diff --git a/lms/djangoapps/django_comment_client/tests/test_helpers.py b/lms/djangoapps/django_comment_client/tests/test_helpers.py index bd67830841..e2c074231f 100644 --- a/lms/djangoapps/django_comment_client/tests/test_helpers.py +++ b/lms/djangoapps/django_comment_client/tests/test_helpers.py @@ -2,10 +2,11 @@ import string import random import collections -from django.test import TestCase +from django.test import TestCase from django_comment_client.helpers import pluralize + class PluralizeTestCase(TestCase): def testPluralize(self): diff --git a/lms/djangoapps/django_comment_client/tests/test_middleware.py b/lms/djangoapps/django_comment_client/tests/test_middleware.py index e3249551b3..55e4c72c75 100644 --- a/lms/djangoapps/django_comment_client/tests/test_middleware.py +++ b/lms/djangoapps/django_comment_client/tests/test_middleware.py @@ -2,27 +2,28 @@ import string import random import collections -from django.test import TestCase +from django.test import TestCase import comment_client import django.http import django_comment_client.middleware as middleware + class AjaxExceptionTestCase(TestCase): -# TODO: check whether the correct error message is produced. +# TODO: check whether the correct error message is produced. # The error message should be the same as the argument to CommentClientError - def setUp(self): - self.a = middleware.AjaxExceptionMiddleware() - self.request1 = django.http.HttpRequest() - self.request0 = django.http.HttpRequest() - self.exception1 = comment_client.CommentClientError('{}') - self.exception0 = ValueError() - self.request1.META['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest" - self.request0.META['HTTP_X_REQUESTED_WITH'] = "SHADOWFAX" - - def test_process_exception(self): - self.assertIsInstance(self.a.process_exception(self.request1, self.exception1), middleware.JsonError) - self.assertIsNone(self.a.process_exception(self.request1, self.exception0)) - self.assertIsNone(self.a.process_exception(self.request0, self.exception1)) - self.assertIsNone(self.a.process_exception(self.request0, self.exception0)) + def setUp(self): + self.a = middleware.AjaxExceptionMiddleware() + self.request1 = django.http.HttpRequest() + self.request0 = django.http.HttpRequest() + self.exception1 = comment_client.CommentClientError('{}') + self.exception0 = ValueError() + self.request1.META['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest" + self.request0.META['HTTP_X_REQUESTED_WITH'] = "SHADOWFAX" + + def test_process_exception(self): + self.assertIsInstance(self.a.process_exception(self.request1, self.exception1), middleware.JsonError) + self.assertIsNone(self.a.process_exception(self.request1, self.exception0)) + self.assertIsNone(self.a.process_exception(self.request0, self.exception1)) + self.assertIsNone(self.a.process_exception(self.request0, self.exception0)) diff --git a/lms/djangoapps/django_comment_client/tests/test_mustache_helpers.py b/lms/djangoapps/django_comment_client/tests/test_mustache_helpers.py index 8638aba67e..5b788b3cc4 100644 --- a/lms/djangoapps/django_comment_client/tests/test_mustache_helpers.py +++ b/lms/djangoapps/django_comment_client/tests/test_mustache_helpers.py @@ -2,25 +2,27 @@ import string import random import collections -from django.test import TestCase +from django.test import TestCase import django_comment_client.mustache_helpers as mustache_helpers + class PluralizeTestCase(TestCase): - def test_pluralize(self): - self.text1 = '0 goat' - self.text2 = '1 goat' - self.text3 = '7 goat' - self.content = 'unused argument' - self.assertEqual(mustache_helpers.pluralize(self.content, self.text1), 'goats') - self.assertEqual(mustache_helpers.pluralize(self.content, self.text2), 'goat') - self.assertEqual(mustache_helpers.pluralize(self.content, self.text3), 'goats') + def test_pluralize(self): + self.text1 = '0 goat' + self.text2 = '1 goat' + self.text3 = '7 goat' + self.content = 'unused argument' + self.assertEqual(mustache_helpers.pluralize(self.content, self.text1), 'goats') + self.assertEqual(mustache_helpers.pluralize(self.content, self.text2), 'goat') + self.assertEqual(mustache_helpers.pluralize(self.content, self.text3), 'goats') + class CloseThreadTextTestCase(TestCase): - - def test_close_thread_text(self): - self.contentClosed = {'closed': True} - self.contentOpen = {'closed': False} - self.assertEqual(mustache_helpers.close_thread_text(self.contentClosed), 'Re-open thread') - self.assertEqual(mustache_helpers.close_thread_text(self.contentOpen), 'Close thread') + + def test_close_thread_text(self): + self.contentClosed = {'closed': True} + self.contentOpen = {'closed': False} + self.assertEqual(mustache_helpers.close_thread_text(self.contentClosed), 'Re-open thread') + self.assertEqual(mustache_helpers.close_thread_text(self.contentOpen), 'Close thread') diff --git a/lms/djangoapps/django_comment_client/tests/test_utils.py b/lms/djangoapps/django_comment_client/tests/test_utils.py index 2e24cbd837..cec006e630 100644 --- a/lms/djangoapps/django_comment_client/tests/test_utils.py +++ b/lms/djangoapps/django_comment_client/tests/test_utils.py @@ -2,7 +2,7 @@ import string import random import collections -from django.test import TestCase +from django.test import TestCase import factory from django.contrib.auth.models import User @@ -14,6 +14,7 @@ import django_comment_client.utils as utils import xmodule.modulestore.django as django + class UserFactory(factory.Factory): FACTORY_FOR = User username = 'robot' @@ -22,20 +23,24 @@ class UserFactory(factory.Factory): is_active = True is_staff = False + class CourseEnrollmentFactory(factory.Factory): - FACTORY_FOR = CourseEnrollment + FACTORY_FOR = CourseEnrollment user = factory.SubFactory(UserFactory) course_id = 'edX/toy/2012_Fall' + class RoleFactory(factory.Factory): FACTORY_FOR = Role name = 'Student' course_id = 'edX/toy/2012_Fall' + class PermissionFactory(factory.Factory): - FACTORY_FOR = Permission + FACTORY_FOR = Permission name = 'create_comment' + class DictionaryTestCase(TestCase): def test_extract(self): d = {'cats': 'meow', 'dogs': 'woof'} @@ -54,11 +59,12 @@ class DictionaryTestCase(TestCase): self.assertEqual(utils.strip_blank(d), expected) def test_merge_dict(self): - d1 ={'cats': 'meow', 'dogs': 'woof'} - d2 ={'lions': 'roar','ducks': 'quack'} - expected ={'cats': 'meow', 'dogs': 'woof','lions': 'roar','ducks': 'quack'} + d1 = {'cats': 'meow', 'dogs': 'woof'} + d2 = {'lions': 'roar', 'ducks': 'quack'} + expected = {'cats': 'meow', 'dogs': 'woof', 'lions': 'roar', 'ducks': 'quack'} self.assertEqual(utils.merge_dict(d1, d2), expected) + class AccessUtilsTestCase(TestCase): def setUp(self): self.course_id = 'edX/toy/2012_Fall' diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index b58e3b30e6..1f1a80e2b4 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -24,21 +24,27 @@ log = logging.getLogger(__name__) _FULLMODULES = None _DISCUSSIONINFO = defaultdict(dict) + def extract(dic, keys): return {k: dic.get(k) for k in keys} + def strip_none(dic): return dict([(k, v) for k, v in dic.iteritems() if v is not None]) + def strip_blank(dic): def _is_blank(v): return isinstance(v, str) and len(v.strip()) == 0 return dict([(k, v) for k, v in dic.iteritems() if not _is_blank(v)]) # TODO should we be checking if d1 and d2 have the same keys with different values? + + def merge_dict(dic1, dic2): return dict(dic1.items() + dic2.items()) + def get_role_ids(course_id): roles = Role.objects.filter(course_id=course_id) staff = list(User.objects.filter(is_staff=True).values_list('id', flat=True)) @@ -47,6 +53,7 @@ def get_role_ids(course_id): roles_with_ids[role.name] = list(role.users.values_list('id', flat=True)) return roles_with_ids + def has_forum_access(uname, course_id, rolename): try: role = Role.objects.get(name=rolename, course_id=course_id) @@ -54,12 +61,14 @@ def has_forum_access(uname, course_id, rolename): return False return role.users.filter(username=uname).exists() + def get_full_modules(): global _FULLMODULES if not _FULLMODULES: _FULLMODULES = modulestore().modules return _FULLMODULES + def get_discussion_id_map(course): """ return a dict of the form {category: modules} @@ -68,18 +77,21 @@ def get_discussion_id_map(course): initialize_discussion_info(course) return _DISCUSSIONINFO[course.id]['id_map'] + def get_discussion_title(course, discussion_id): global _DISCUSSIONINFO initialize_discussion_info(course) title = _DISCUSSIONINFO[course.id]['id_map'].get(discussion_id, {}).get('title', '(no title)') return title + def get_discussion_category_map(course): global _DISCUSSIONINFO initialize_discussion_info(course) return filter_unstarted_categories(_DISCUSSIONINFO[course.id]['category_map']) + def filter_unstarted_categories(category_map): now = time.gmtime() @@ -117,6 +129,7 @@ def filter_unstarted_categories(category_map): return result_map + def sort_map_entries(category_map): things = [] for title, entry in category_map["entries"].items(): @@ -211,7 +224,7 @@ def initialize_discussion_info(course): # TODO. BUG! : course location is not unique across multiple course runs! # (I think Kevin already noticed this) Need to send course_id with requests, store it # in the backend. - default_topics = {'General': {'id' :course.location.html_id()}} + default_topics = {'General': {'id': course.location.html_id()}} discussion_topics = course.metadata.get('discussion_topics', default_topics) for topic, entry in discussion_topics.items(): category_map['entries'][topic] = {"id": entry["id"], @@ -223,12 +236,14 @@ def initialize_discussion_info(course): _DISCUSSIONINFO[course.id]['category_map'] = category_map _DISCUSSIONINFO[course.id]['timestamp'] = datetime.now() + class JsonResponse(HttpResponse): def __init__(self, data=None): content = simplejson.dumps(data) super(JsonResponse, self).__init__(content, mimetype='application/json; charset=utf-8') + class JsonError(HttpResponse): def __init__(self, error_messages=[], status=400): if isinstance(error_messages, str): @@ -239,14 +254,17 @@ class JsonError(HttpResponse): super(JsonError, self).__init__(content, mimetype='application/json; charset=utf-8', status=status) + class HtmlResponse(HttpResponse): def __init__(self, html=''): super(HtmlResponse, self).__init__(html, content_type='text/plain') + class ViewNameMiddleware(object): def process_view(self, request, view_func, view_args, view_kwargs): request.view_name = view_func.__name__ + class QueryCountDebugMiddleware(object): """ This middleware will log the number of queries run @@ -272,6 +290,7 @@ class QueryCountDebugMiddleware(object): log.info('%s queries run, total %s seconds' % (len(connection.queries), total_time)) return response + def get_ability(course_id, content, user): return { 'editable': check_permissions_by_view(user, course_id, content, "update_thread" if content['type'] == 'thread' else "update_comment"), @@ -283,6 +302,8 @@ def get_ability(course_id, content, user): } #TODO: RENAME + + def get_annotated_content_info(course_id, content, user, user_info): """ Get metadata for an individual content (thread or comment) @@ -299,6 +320,8 @@ def get_annotated_content_info(course_id, content, user, user_info): } #TODO: RENAME + + def get_annotated_content_infos(course_id, thread, user, user_info): """ Get metadata for a thread and its children @@ -311,6 +334,7 @@ def get_annotated_content_infos(course_id, thread, user, user_info): annotate(thread) return infos + def get_metadata_for_threads(course_id, threads, user, user_info): def infogetter(thread): return get_annotated_content_infos(course_id, thread, user, user_info) @@ -319,13 +343,17 @@ def get_metadata_for_threads(course_id, threads, user, user_info): return metadata # put this method in utils.py to avoid circular import dependency between helpers and mustache_helpers + + def url_for_tags(course_id, tags): return reverse('django_comment_client.forum.views.forum_form_discussion', args=[course_id]) + '?' + urllib.urlencode({'tags': tags}) + def render_mustache(template_name, dictionary, *args, **kwargs): template = middleware.lookup['main'].get_template(template_name).source return pystache.render(template, dictionary) + def permalink(content): if content['type'] == 'thread': return reverse('django_comment_client.forum.views.single_thread', @@ -334,6 +362,7 @@ def permalink(content): return reverse('django_comment_client.forum.views.single_thread', args=[content['course_id'], content['commentable_id'], content['thread_id']]) + '#' + content['id'] + def extend_content(content): roles = {} if content.get('user_id'): @@ -349,10 +378,11 @@ def extend_content(content): 'raw_tags': ','.join(content.get('tags', [])), 'permalink': permalink(content), 'roles': roles, - 'updated': content['created_at']!=content['updated_at'], + 'updated': content['created_at'] != content['updated_at'], } return merge_dict(content, content_info) + def get_courseware_context(content, course): id_map = get_discussion_id_map(course) id = content['commentable_id'] @@ -361,13 +391,14 @@ def get_courseware_context(content, course): location = id_map[id]["location"].url() title = id_map[id]["title"] (course_id, chapter, section, position) = path_to_location(modulestore(), course.id, location) - url = reverse('courseware_position', kwargs={"course_id":course_id, - "chapter":chapter, - "section":section, - "position":position}) + url = reverse('courseware_position', kwargs={"course_id": course_id, + "chapter": chapter, + "section": section, + "position": position}) content_info = {"courseware_url": url, "courseware_title": title} return content_info + def safe_content(content): fields = [ 'id', 'title', 'body', 'course_id', 'anonymous', 'anonymous_to_peers', diff --git a/lms/djangoapps/instructor/management/commands/compute_grades.py b/lms/djangoapps/instructor/management/commands/compute_grades.py index 462833ba3c..92db04f09a 100644 --- a/lms/djangoapps/instructor/management/commands/compute_grades.py +++ b/lms/djangoapps/instructor/management/commands/compute_grades.py @@ -3,7 +3,9 @@ # django management command: dump grades to csv files # for use by batch processes -import os, sys, string +import os +import sys +import string import datetime import json @@ -15,6 +17,7 @@ from xmodule.modulestore.django import modulestore from django.conf import settings from django.core.management.base import BaseCommand + class Command(BaseCommand): help = "Compute grades for all students in a course, and store result in DB.\n" help += "Usage: compute_grades course_id_or_dir \n" @@ -25,7 +28,7 @@ class Command(BaseCommand): print "args = ", args - if len(args)>0: + if len(args) > 0: course_id = args[0] else: print self.help @@ -46,7 +49,3 @@ class Command(BaseCommand): print "Computing grades for %s" % (course.id) offline_grade_calculation(course.id) - - - - diff --git a/lms/djangoapps/instructor/management/commands/dump_grades.py b/lms/djangoapps/instructor/management/commands/dump_grades.py index 65825271f3..13f86c0e0f 100644 --- a/lms/djangoapps/instructor/management/commands/dump_grades.py +++ b/lms/djangoapps/instructor/management/commands/dump_grades.py @@ -3,7 +3,9 @@ # django management command: dump grades to csv files # for use by batch processes -import os, sys, string +import os +import sys +import string import datetime import json @@ -14,6 +16,7 @@ from xmodule.modulestore.django import modulestore from django.conf import settings from django.core.management.base import BaseCommand + class Command(BaseCommand): help = "dump grades to CSV file. Usage: dump_grades course_id_or_dir filename dump_type\n" help += " course_id_or_dir: either course_id or course_dir\n" @@ -32,12 +35,12 @@ class Command(BaseCommand): fn = "grades.csv" get_raw_scores = False - if len(args)>0: + if len(args) > 0: course_id = args[0] - if len(args)>1: + if len(args) > 1: fn = args[1] - if len(args)>2: - get_raw_scores = args[2].lower()=='raw' + if len(args) > 2: + get_raw_scores = args[2].lower() == 'raw' request = self.DummyRequest() try: @@ -54,15 +57,15 @@ class Command(BaseCommand): print "-----------------------------------------------------------------------------" print "Dumping grades from %s to file %s (get_raw_scores=%s)" % (course.id, fn, get_raw_scores) datatable = get_student_grade_summary_data(request, course, course.id, get_raw_scores=get_raw_scores) - - fp = open(fn,'w') - + + fp = open(fn, 'w') + writer = csv.writer(fp, dialect='excel', quotechar='"', quoting=csv.QUOTE_ALL) writer.writerow(datatable['header']) for datarow in datatable['data']: encoded_row = [unicode(s).encode('utf-8') for s in datarow] writer.writerow(encoded_row) - + fp.close() print "Done: %d records dumped" % len(datatable['data']) @@ -74,6 +77,3 @@ class Command(BaseCommand): return 'edx.mit.edu' def is_secure(self): return False - - - diff --git a/lms/djangoapps/instructor/offline_gradecalc.py b/lms/djangoapps/instructor/offline_gradecalc.py index 7c102805b4..8182c4e58a 100644 --- a/lms/djangoapps/instructor/offline_gradecalc.py +++ b/lms/djangoapps/instructor/offline_gradecalc.py @@ -31,7 +31,7 @@ class MyEncoder(JSONEncoder): def offline_grade_calculation(course_id): ''' - Compute grades for all students for a specified course, and save results to the DB. + Compute grades for all students for a specified course, and save results to the DB. ''' tstart = time.time() @@ -59,16 +59,16 @@ def offline_grade_calculation(course_id): ocg, created = models.OfflineComputedGrade.objects.get_or_create(user=student, course_id=course_id) ocg.gradeset = gs ocg.save() - print "%s done" % student # print statement used because this is run by a management command + print "%s done" % student # print statement used because this is run by a management command tend = time.time() dt = tend - tstart - + ocgl = models.OfflineComputedGradeLog(course_id=course_id, seconds=dt, nstudents=len(enrolled_students)) ocgl.save() print ocgl print "All Done!" - + def offline_grades_available(course_id): ''' @@ -80,7 +80,7 @@ def offline_grades_available(course_id): return False return ocgl.latest('created') - + def student_grades(student, request, course, keep_raw_scores=False, use_offline=False): ''' This is the main interface to get grades. It has the same parameters as grades.grade, as well @@ -89,15 +89,11 @@ def student_grades(student, request, course, keep_raw_scores=False, use_offline= if not use_offline: return grades.grade(student, request, course, keep_raw_scores=keep_raw_scores) - + try: ocg = models.OfflineComputedGrade.objects.get(user=student, course_id=course.id) except models.OfflineComputedGrade.DoesNotExist: - return dict(raw_scores=[], section_breakdown=[], + return dict(raw_scores=[], section_breakdown=[], msg='Error: no offline gradeset available for %s, %s' % (student, course.id)) - + return json.loads(ocg.gradeset) - - - - diff --git a/lms/djangoapps/instructor/tests.py b/lms/djangoapps/instructor/tests.py index e2ee878021..2610e57422 100644 --- a/lms/djangoapps/instructor/tests.py +++ b/lms/djangoapps/instructor/tests.py @@ -71,13 +71,13 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): response = self.client.post(url, {'action': 'Download CSV of all student grades for this course'}) msg += "instructor dashboard download csv grades: response = '{0}'\n".format(response) - self.assertEqual(response['Content-Type'],'text/csv',msg) + self.assertEqual(response['Content-Type'], 'text/csv', msg) cdisp = response['Content-Disposition'] msg += "Content-Disposition = '%s'\n" % cdisp self.assertEqual(cdisp, 'attachment; filename=grades_{0}.csv'.format(course.id), msg) - body = response.content.replace('\r','') + body = response.content.replace('\r', '') msg += "body = '{0}'\n".format(body) # All the not-actually-in-the-course hw and labs come from the @@ -89,9 +89,10 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): self.assertEqual(body, expected_body, msg) -FORUM_ROLES = [ FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA ] -FORUM_ADMIN_ACTION_SUFFIX = { FORUM_ROLE_ADMINISTRATOR : 'admin', FORUM_ROLE_MODERATOR : 'moderator', FORUM_ROLE_COMMUNITY_TA : 'community TA'} -FORUM_ADMIN_USER = { FORUM_ROLE_ADMINISTRATOR : 'forumadmin', FORUM_ROLE_MODERATOR : 'forummoderator', FORUM_ROLE_COMMUNITY_TA : 'forummoderator'} +FORUM_ROLES = [FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA] +FORUM_ADMIN_ACTION_SUFFIX = {FORUM_ROLE_ADMINISTRATOR: 'admin', FORUM_ROLE_MODERATOR: 'moderator', FORUM_ROLE_COMMUNITY_TA: 'community TA'} +FORUM_ADMIN_USER = {FORUM_ROLE_ADMINISTRATOR: 'forumadmin', FORUM_ROLE_MODERATOR: 'forummoderator', FORUM_ROLE_COMMUNITY_TA: 'forummoderator'} + def action_name(operation, rolename): if operation == 'List': @@ -146,7 +147,7 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): for action in ['Add', 'Remove']: for rolename in FORUM_ROLES: response = self.client.post(url, {'action': action_name(action, rolename), FORUM_ADMIN_USER[rolename]: username}) - self.assertTrue(response.content.find('Error: unknown username "{0}"'.format(username))>=0) + self.assertTrue(response.content.find('Error: unknown username "{0}"'.format(username)) >= 0) def test_add_forum_admin_users_for_missing_roles(self): course = self.toy @@ -155,7 +156,7 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): for action in ['Add', 'Remove']: for rolename in FORUM_ROLES: response = self.client.post(url, {'action': action_name(action, rolename), FORUM_ADMIN_USER[rolename]: username}) - self.assertTrue(response.content.find('Error: unknown rolename "{0}"'.format(rolename))>=0) + self.assertTrue(response.content.find('Error: unknown rolename "{0}"'.format(rolename)) >= 0) def test_remove_forum_admin_users_for_missing_users(self): course = self.toy @@ -165,7 +166,7 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): action = 'Remove' for rolename in FORUM_ROLES: response = self.client.post(url, {'action': action_name(action, rolename), FORUM_ADMIN_USER[rolename]: username}) - self.assertTrue(response.content.find('Error: user "{0}" does not have rolename "{1}"'.format(username, rolename))>=0) + self.assertTrue(response.content.find('Error: user "{0}" does not have rolename "{1}"'.format(username, rolename)) >= 0) def test_add_and_remove_forum_admin_users(self): course = self.toy @@ -174,10 +175,10 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): username = 'u2' for rolename in FORUM_ROLES: response = self.client.post(url, {'action': action_name('Add', rolename), FORUM_ADMIN_USER[rolename]: username}) - self.assertTrue(response.content.find('Added "{0}" to "{1}" forum role = "{2}"'.format(username, course.id, rolename))>=0) + self.assertTrue(response.content.find('Added "{0}" to "{1}" forum role = "{2}"'.format(username, course.id, rolename)) >= 0) self.assertTrue(has_forum_access(username, course.id, rolename)) response = self.client.post(url, {'action': action_name('Remove', rolename), FORUM_ADMIN_USER[rolename]: username}) - self.assertTrue(response.content.find('Removed "{0}" from "{1}" forum role = "{2}"'.format(username, course.id, rolename))>=0) + self.assertTrue(response.content.find('Removed "{0}" from "{1}" forum role = "{2}"'.format(username, course.id, rolename)) >= 0) self.assertFalse(has_forum_access(username, course.id, rolename)) def test_add_and_read_forum_admin_users(self): @@ -189,7 +190,7 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): # perform an add, and follow with a second identical add: self.client.post(url, {'action': action_name('Add', rolename), FORUM_ADMIN_USER[rolename]: username}) response = self.client.post(url, {'action': action_name('Add', rolename), FORUM_ADMIN_USER[rolename]: username}) - self.assertTrue(response.content.find('Error: user "{0}" already has rolename "{1}", cannot add'.format(username, rolename))>=0) + self.assertTrue(response.content.find('Error: user "{0}" already has rolename "{1}", cannot add'.format(username, rolename)) >= 0) self.assertTrue(has_forum_access(username, course.id, rolename)) def test_add_nonstaff_forum_admin_users(self): @@ -199,7 +200,7 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): username = 'u1' rolename = FORUM_ROLE_ADMINISTRATOR response = self.client.post(url, {'action': action_name('Add', rolename), FORUM_ADMIN_USER[rolename]: username}) - self.assertTrue(response.content.find('Error: user "{0}" should first be added as staff'.format(username))>=0) + self.assertTrue(response.content.find('Error: user "{0}" should first be added as staff'.format(username)) >= 0) def test_list_forum_admin_users(self): course = self.toy @@ -213,12 +214,10 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): self.assertTrue(has_forum_access(username, course.id, rolename)) response = self.client.post(url, {'action': action_name('List', rolename), FORUM_ADMIN_USER[rolename]: username}) for header in ['Username', 'Full name', 'Roles']: - self.assertTrue(response.content.find('

      4. {0}{0}{0}{0}{0}{0}