def add_users_to_cohort(request, course_key_string, cohort_id): """ Return json dict of: {'success': True, 'added': [{'username': ..., 'name': ..., 'email': ...}, ...], 'changed': [{'username': ..., 'name': ..., 'email': ..., 'previous_cohort': ...}, ...], 'present': [str1, str2, ...], # already there 'unknown': [str1, str2, ...]} Raises Http404 if the cohort cannot be found for the given course. """ # this is a string when we get it here course_key = SlashSeparatedCourseKey.from_deprecated_string(course_key_string) get_course_with_access(request.user, 'staff', course_key) try: cohort = cohorts.get_cohort_by_id(course_key, cohort_id) except CourseUserGroup.DoesNotExist: raise Http404("Cohort (ID {cohort_id}) not found for {course_key_string}".format( cohort_id=cohort_id, course_key_string=course_key_string )) users = request.POST.get('users', '') added = [] changed = [] present = [] unknown = [] for username_or_email in split_by_comma_and_whitespace(users): if not username_or_email: continue try: (user, previous_cohort) = cohorts.add_user_to_cohort(cohort, username_or_email) info = { 'username': user.username, 'name': user.profile.name, 'email': user.email, } if previous_cohort: info['previous_cohort'] = previous_cohort changed.append(info) else: added.append(info) except ValueError: present.append(username_or_email) except User.DoesNotExist: unknown.append(username_or_email) return json_http_response({'success': True, 'added': added, 'changed': changed, 'present': present, 'unknown': unknown})
def add_cohort(request, course_key_string): """ Return json of dict: {'success': True, 'cohort': {'id': id, 'name': name}} or {'success': False, 'msg': error_msg} if there's an error """ # this is a string when we get it here course_key = SlashSeparatedCourseKey.from_deprecated_string(course_key_string) get_course_with_access(request.user, 'staff', course_key) name = request.POST.get("name") if not name: return json_http_response({'success': False, 'msg': "No name specified"}) try: cohort = cohorts.add_cohort(course_key, name) except ValueError as err: return json_http_response({'success': False, 'msg': str(err)}) return json_http_response({ 'success': 'True', 'cohort': { 'id': cohort.id, 'name': cohort.name } })
def remove_user_from_cohort(request, course_key_string, cohort_id): """ Expects 'username': username in POST data. Return json dict of: {'success': True} or {'success': False, 'msg': error_msg} """ # this is a string when we get it here course_key = SlashSeparatedCourseKey.from_deprecated_string(course_key_string) get_course_with_access(request.user, 'staff', course_key) username = request.POST.get('username') if username is None: return json_http_response({'success': False, 'msg': 'No username specified'}) cohort = cohorts.get_cohort_by_id(course_key, cohort_id) try: user = User.objects.get(username=username) cohort.users.remove(user) return json_http_response({'success': True}) except User.DoesNotExist: log.debug('no user') return json_http_response({'success': False, 'msg': "No user '{0}'".format(username)})
def remove_user_from_cohort(request, course_key_string, cohort_id): """ Expects 'username': username in POST data. Return json dict of: {'success': True} or {'success': False, 'msg': error_msg} """ # this is a string when we get it here course_key = SlashSeparatedCourseKey.from_deprecated_string(course_key_string) get_course_with_access(request.user, "staff", course_key) username = request.POST.get("username") if username is None: return json_http_response({"success": False, "msg": "No username specified"}) try: user = User.objects.get(username=username) except User.DoesNotExist: log.debug("no user") return json_http_response({"success": False, "msg": "No user '{0}'".format(username)}) try: membership = CohortMembership.objects.get(user=user, course_id=course_key) membership.delete() except CohortMembership.DoesNotExist: pass return json_http_response({"success": True})
def course_cohort_settings_handler(request, course_key_string): """ The restful handler for cohort setting requests. Requires JSON. This will raise 404 if user is not staff. GET Returns the JSON representation of cohort settings for the course. PATCH Updates the cohort settings for the course. Returns the JSON representation of updated settings. """ course_key = CourseKey.from_string(course_key_string) # Although this course data is not used this method will return 404 is user is not staff get_course_with_access(request.user, 'staff', course_key) if request.method == 'PATCH': if 'is_cohorted' not in request.json: return JsonResponse({"error": unicode("Bad Request")}, 400) is_cohorted = request.json.get('is_cohorted') try: cohorts.set_course_cohorted(course_key, is_cohorted) except ValueError as err: # Note: error message not translated because it is not exposed to the user (UI prevents this state). return JsonResponse({"error": unicode(err)}, 400) return JsonResponse(_get_course_cohort_settings_representation( cohorts.get_course_cohort_id(course_key), cohorts.is_course_cohorted(course_key) ))
def hint_manager(request, course_id): """ The URL landing function for all calls to the hint manager, both POST and GET. """ try: get_course_with_access(request.user, course_id, 'staff', depth=None) except Http404: out = 'Sorry, but students are not allowed to access the hint manager!' return HttpResponse(out) if request.method == 'GET': out = get_hints(request, course_id, 'mod_queue') out.update({'error': ''}) return render_to_response('instructor/hint_manager.html', out) field = request.POST['field'] if not (field == 'mod_queue' or field == 'hints'): # Invalid field. (Don't let users continue - they may overwrite other db's) out = 'Error in hint manager - an invalid field was accessed.' return HttpResponse(out) switch_dict = { 'delete hints': delete_hints, 'switch fields': lambda *args: None, # Takes any number of arguments, returns None. 'change votes': change_votes, 'add hint': add_hint, 'approve': approve, } # Do the operation requested, and collect any error messages. error_text = switch_dict[request.POST['op']](request, course_id, field) if error_text is None: error_text = '' render_dict = get_hints(request, course_id, field) render_dict.update({'error': error_text}) rendered_html = render_to_string('instructor/hint_manager_inner.html', render_dict) return HttpResponse(json.dumps({'success': True, 'contents': rendered_html}))
def remove_user_from_cohort(request, course_id, cohort_id): """ Expects 'username': username in POST data. Return json dict of: {'success': True} or {'success': False, 'msg': error_msg} """ get_course_with_access(request.user, course_id, 'staff') username = request.POST.get('username') if username is None: return json_http_response({'success': False, 'msg': 'No username specified'}) cohort = cohorts.get_cohort_by_id(course_id, cohort_id) try: user = User.objects.get(username=username) cohort.users.remove(user) return json_http_response({'success': True}) except User.DoesNotExist: log.debug('no user') return json_http_response({'success': False, 'msg': "No user '{0}'".format(username)})
def hint_manager(request, course_id): """ The URL landing function for all calls to the hint manager, both POST and GET. """ try: get_course_with_access(request.user, course_id, "staff", depth=None) except Http404: out = "Sorry, but students are not allowed to access the hint manager!" return HttpResponse(out) if request.method == "GET": out = get_hints(request, course_id, "mod_queue") out.update({"error": ""}) return render_to_response("instructor/hint_manager.html", out) field = request.POST["field"] if not (field == "mod_queue" or field == "hints"): # Invalid field. (Don't let users continue - they may overwrite other db's) out = "Error in hint manager - an invalid field was accessed." return HttpResponse(out) switch_dict = { "delete hints": delete_hints, "switch fields": lambda *args: None, # Takes any number of arguments, returns None. "change votes": change_votes, "add hint": add_hint, "approve": approve, } # Do the operation requested, and collect any error messages. error_text = switch_dict[request.POST["op"]](request, course_id, field) if error_text is None: error_text = "" render_dict = get_hints(request, course_id, field) render_dict.update({"error": error_text}) rendered_html = render_to_string("instructor/hint_manager_inner.html", render_dict) return HttpResponse(json.dumps({"success": True, "contents": rendered_html}))
def users(request, course_id): """ Given a `username` query parameter, find matches for users in the forum for this course. Only exact matches are supported here, so the length of the result set will either be 0 or 1. """ course_key = CourseKey.from_string(course_id) try: get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) except Http404: # course didn't exist, or requesting user does not have access to it. return JsonError(status=404) try: username = request.GET['username'] except KeyError: # 400 is default status for JsonError return JsonError(["username parameter is required"]) user_objs = [] try: matched_user = User.objects.get(username=username) cc_user = cc.User.from_django_user(matched_user) cc_user.course_id = course_key cc_user.retrieve(complete=False) if (cc_user['threads_count'] + cc_user['comments_count']) > 0: user_objs.append({ 'id': matched_user.id, 'username': matched_user.username, }) except User.DoesNotExist: pass return JsonResponse({"users": user_objs})
def add_cohort(request, course_id): """ Return json of dict: {'success': True, 'cohort': {'id': id, 'name': name}} or {'success': False, 'msg': error_msg} if there's an error """ get_course_with_access(request.user, course_id, 'staff') name = request.POST.get("name") if not name: return json_http_response({'success': False, 'msg': "No name specified"}) try: cohort = cohorts.add_cohort(course_id, name) except ValueError as err: return json_http_response({'success': False, 'msg': str(err)}) return json_http_response({'success': 'True', 'cohort': { 'id': cohort.id, 'name': cohort.name }})
def hint_manager(request, course_id): try: get_course_with_access(request.user, course_id, 'staff', depth=None) except Http404: out = 'Sorry, but students are not allowed to access the hint manager!' return HttpResponse(out) if request.method == 'GET': out = get_hints(request, course_id, 'mod_queue') return render_to_response('courseware/hint_manager.html', out) field = request.POST['field'] if not (field == 'mod_queue' or field == 'hints'): # Invalid field. (Don't let users continue - they may overwrite other db's) out = 'Error in hint manager - an invalid field was accessed.' return HttpResponse(out) if request.POST['op'] == 'delete hints': delete_hints(request, course_id, field) if request.POST['op'] == 'switch fields': pass if request.POST['op'] == 'change votes': change_votes(request, course_id, field) if request.POST['op'] == 'add hint': add_hint(request, course_id, field) if request.POST['op'] == 'approve': approve(request, course_id, field) rendered_html = render_to_string('courseware/hint_manager_inner.html', get_hints(request, course_id, field)) return HttpResponse(json.dumps({'success': True, 'contents': rendered_html}))
def mobi_course_action(request, course_id, action): try: course_id_bak = course_id.replace('.', '/') if action in ["updates", "handouts", "structure"]: course = get_course_with_access(request.user, course_id_bak, 'see_exists') user = request.user if not user: user = AnonymousUser() registered = registered_for_course(course, user) if action == "updates" and registered: course_updates = get_course_info_section(request, course, action) return JsonResponse(parse_updates_html_str(course_updates)) elif action == "handouts" and registered: course_handouts = get_course_info_section(request, course, action) return JsonResponse({"handouts": course_handouts}) elif action == "structure": return JsonResponse(_course_json(course, course.location.course_id)) else: raise Exception else: course = get_course_with_access(request.user, course_id_bak, 'see_exists') return JsonResponse(mobi_course_info(request, course)) except: return JsonResponse({"success": False, "errmsg": "access denied!"})
def test_get_course_with_access(self): user = UserFactory.create() course = CourseFactory.create(visible_to_staff_only=True) with self.assertRaises(CoursewareAccessException) as error: get_course_with_access(user, 'load', course.id) self.assertEqual(error.exception.message, "Course not found.") self.assertEqual(error.exception.access_response.error_code, "not_visible_to_user") self.assertFalse(error.exception.access_response.has_access)
def debug_cohort_mgmt(request, course_id): """ Debugging view for dev. """ # add staff check to make sure it's safe if it's accidentally deployed. get_course_with_access(request.user, course_id, 'staff') context = {'cohorts_ajax_url': reverse('cohorts', kwargs={'course_id': course_id})} return render_to_response('/course_groups/debug.html', context)
def debug_cohort_mgmt(request, course_key_string): """ Debugging view for dev. """ # this is a string when we get it here course_key = SlashSeparatedCourseKey.from_deprecated_string(course_key_string) # add staff check to make sure it's safe if it's accidentally deployed. get_course_with_access(request.user, "staff", course_key) context = {"cohorts_url": reverse("cohorts", kwargs={"course_key": course_key.to_deprecated_string()})} return render_to_response("/course_groups/debug.html", context)
def debug_cohort_mgmt(request, course_key_string): """ Debugging view for dev. """ # this is a string when we get it here course_key = CourseKey.from_string(course_key_string) # add staff check to make sure it's safe if it's accidentally deployed. get_course_with_access(request.user, 'staff', course_key) context = {'cohorts_url': reverse( 'cohorts', kwargs={'course_key': text_type(course_key)} )} return render_to_response('/course_groups/debug.html', context)
def add_users_to_cohort(request, course_id, cohort_id): """ Return json dict of: {'success': True, 'added': [{'username': ..., 'name': ..., 'email': ...}, ...], 'changed': [{'username': ..., 'name': ..., 'email': ..., 'previous_cohort': ...}, ...], 'present': [str1, str2, ...], # already there 'unknown': [str1, str2, ...]} """ get_course_with_access(request.user, course_id, 'staff') cohort = cohorts.get_cohort_by_id(course_id, cohort_id) users = request.POST.get('users', '') added = [] changed = [] present = [] unknown = [] for username_or_email in split_by_comma_and_whitespace(users): if not username_or_email: continue try: (user, previous_cohort) = cohorts.add_user_to_cohort(cohort, username_or_email) info = { 'username': user.username, 'name': user.profile.name, 'email': user.email, } if previous_cohort: info['previous_cohort'] = previous_cohort changed.append(info) else: added.append(info) except ValueError: present.append(username_or_email) except User.DoesNotExist: unknown.append(username_or_email) return json_http_response({'success': True, 'added': added, 'changed': changed, 'present': present, 'unknown': unknown})
def get_course_time(self, user_id, course_id, type='', add_time_out=False): count_time = self.get_adjustment_time(user_id, type, course_id) if type == 'courseware': if course_id is None: course_time = {} results = self.collection_page.find({'user_id': user_id}) for data in results: try: course_time[data['course_id']]['time'] += int(data['time']) except: max_time = int(get_course_with_access(user_id, data['course_id'], 'load').maximum_units_time) course_time[data['course_id']] = {'time': int(data['time']), 'max_time': max_time} for cid in course_time: if course_time[cid]['time'] > course_time[cid]['max_time']: course_time[cid]['time'] = course_time[cid]['max_time'] count_time += course_time[cid]['time'] else: results = self.collection_page.find( { 'user_id': user_id, 'course_id': course_id } ) for data in results: count_time += int(data['time']) max_time = int(get_course_with_access(user_id, course_id, 'load').maximum_units_time) if add_time_out: fix_time = count_time - settings.PEPPER_SESSION_EXPIRY if fix_time >= 0: count_time = fix_time if count_time > max_time: count_time = max_time return count_time elif type == 'discussion': if course_id is None: results = self.collection_discussion.find({'user_id': user_id}) else: results = self.collection_discussion.find( { 'user_id': user_id, 'course_id': course_id } ) elif type == 'portfolio': results = self.collection_portfolio.find({'user_id': user_id}) for data in results: count_time += int(data['time']) return count_time
def list_cohorts(request, course_id): """ Return json dump of dict: {'success': True, 'cohorts': [{'name': name, 'id': id}, ...]} """ get_course_with_access(request.user, course_id, 'staff') all_cohorts = [{'name': c.name, 'id': c.id} for c in cohorts.get_course_cohorts(course_id)] return json_http_response({'success': True, 'cohorts': all_cohorts})
def users_in_cohort(request, course_key_string, cohort_id): """ Return users in the cohort. Show up to 100 per page, and page using the 'page' GET attribute in the call. Format: Returns: Json dump of dictionary in the following format: {'success': True, 'page': page, 'num_pages': paginator.num_pages, 'users': [{'username': ..., 'email': ..., 'name': ...}] } """ # this is a string when we get it here course_key = SlashSeparatedCourseKey.from_deprecated_string( course_key_string) get_course_with_access(request.user, 'staff', course_key) # this will error if called with a non-int cohort_id. That's ok--it # shoudn't happen for valid clients. cohort = cohorts.get_cohort_by_id(course_key, int(cohort_id)) paginator = Paginator(cohort.users.all(), 100) page = request.GET.get('page') try: users = paginator.page(page) except PageNotAnInteger: # return the first page page = 1 users = paginator.page(page) except EmptyPage: # Page is out of range. Return last page page = paginator.num_pages contacts = paginator.page(page) user_info = [{ 'username': u.username, 'email': u.email, 'name': '{0} {1}'.format(u.first_name, u.last_name) } for u in users] return json_http_response({ 'success': True, 'page': page, 'num_pages': paginator.num_pages, 'users': user_info })
def static_tab(request, course_id, tab_slug): """ Display the courses tab with the given name. Assumes the course_id is in a valid format. """ try: course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) except InvalidKeyError: raise Http404 course = get_course_with_access(request.user, 'load', course_key) tab = CourseTabList.get_tab_by_slug(course.tabs, tab_slug) if tab is None: raise Http404 contents = get_static_tab_contents( request, course, tab ) if contents is None: raise Http404 return render_to_response('courseware/static_tab.html', { 'course': course, 'tab': tab, 'tab_contents': contents, })
def forum_form_discussion(request, course_key): """ Renders the main Discussion page, potentially filtered by a search query """ course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) if request.is_ajax(): user = cc.User.from_django_user(request.user) user_info = user.to_dict() try: unsafethreads, query_params = get_threads(request, course, user_info) # This might process a search query is_staff = has_permission(request.user, 'openclose_thread', course.id) threads = [utils.prepare_content(thread, course_key, is_staff) for thread in unsafethreads] except cc.utils.CommentClientMaintenanceError: return HttpResponseServerError('Forum is in maintenance mode', status=status.HTTP_503_SERVICE_UNAVAILABLE) except ValueError: return HttpResponseServerError("Invalid group_id") with function_trace("get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info) with function_trace("add_courseware_context"): add_courseware_context(threads, course, request.user) return utils.JsonResponse({ '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'], 'corrected_text': query_params['corrected_text'], }) else: course_id = unicode(course.id) tab_view = CourseTabView() return tab_view.get(request, course_id, 'discussion')
def course_info(request, course_id): """ Display the course's info.html, or 404 if there is no such course. Assumes the course_id is in a valid format. """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(request.user, 'load', course_key) staff_access = has_access(request.user, 'staff', course) masq = setup_masquerade(request, staff_access) # allow staff to toggle masquerade on info page reverifications = fetch_reverify_banner_info(request, course_key) studio_url = get_studio_url(course_key, 'course_info') context = { 'request': request, 'course_id': course_key.to_deprecated_string(), 'cache': None, 'course': course, 'staff_access': staff_access, 'masquerade': masq, 'studio_url': studio_url, 'reverifications': reverifications, } return render_to_response('courseware/info.html', context)
def inline_discussion(request, course_key, discussion_id): """ Renders JSON for DiscussionModules """ nr_transaction = newrelic.agent.current_transaction() course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) cc_user = cc.User.from_django_user(request.user) user_info = cc_user.to_dict() try: threads, query_params = get_threads(request, course, discussion_id, per_page=INLINE_THREADS_PER_PAGE) except ValueError: return HttpResponseBadRequest("Invalid group_id") with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info) is_staff = has_permission(request.user, 'openclose_thread', course.id) threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads] with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context(threads, course, request.user) return utils.JsonResponse({ 'is_commentable_cohorted': is_commentable_cohorted(course_key, discussion_id), 'discussion_data': threads, 'user_info': user_info, 'annotated_content_info': annotated_content_info, 'page': query_params['page'], 'num_pages': query_params['num_pages'], 'roles': utils.get_role_ids(course_key), 'course_settings': make_course_settings(course, request.user) })
def mktg_course_about(request, course_id): """ This is the button that gets put into an iframe on the Drupal site """ try: course = get_course_with_access(request.user, course_id, "see_exists") except (ValueError, Http404) as e: # if a course does not exist yet, display a coming # soon button return render_to_response("courseware/mktg_coming_soon.html", {"course_id": course_id}) registered = registered_for_course(course, request.user) if has_access(request.user, course, "load"): course_target = reverse("info", args=[course.id]) else: course_target = reverse("about_course", args=[course.id]) allow_registration = has_access(request.user, course, "enroll") show_courseware_link = has_access(request.user, course, "load") or settings.FEATURES.get("ENABLE_LMS_MIGRATION") course_modes = CourseMode.modes_for_course(course.id) return render_to_response( "courseware/mktg_course_about.html", { "course": course, "registered": registered, "allow_registration": allow_registration, "course_target": course_target, "show_courseware_link": show_courseware_link, "course_modes": course_modes, }, )
def test_module_render_with_jump_to_id(self): """ This test validates that the /jump_to_id/<id> shorthand for intracourse linking works assertIn expected. Note there's a HTML element in the 'toy' course with the url_name 'toyjumpto' which defines this linkage """ mock_request = MagicMock() mock_request.user = self.mock_user course = get_course_with_access(self.mock_user, self.course_id, 'load') field_data_cache = FieldDataCache.cache_for_descriptor_descendents( self.course_id, self.mock_user, course, depth=2) module = render.get_module( self.mock_user, mock_request, Location('i4x', 'edX', 'toy', 'html', 'toyjumpto'), field_data_cache, self.course_id ) # get the rendered HTML output which should have the rewritten link html = module.render('student_view').content # See if the url got rewritten to the target link # note if the URL mapping changes then this assertion will break self.assertIn('/courses/' + self.course_id + '/jump_to_id/vertical_test', html)
def index(request, course_id, book_index, page=None): """ Serve static image-based textbooks. """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(request.user, 'load', course_key) staff_access = bool(has_access(request.user, 'staff', course)) book_index = int(book_index) if book_index < 0 or book_index >= len(course.textbooks): raise Http404("Invalid book index value: {0}".format(book_index)) textbook = course.textbooks[book_index] table_of_contents = textbook.table_of_contents if page is None: page = textbook.start_page return render_to_response( 'staticbook.html', { '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, 'staff_access': staff_access, }, )
def _create_base_discussion_view_context(request, course_key): """ Returns the default template context for rendering any discussion view. """ user = request.user cc_user = cc.User.from_django_user(user) user_info = cc_user.to_dict() course = get_course_with_access(user, 'load', course_key, check_if_enrolled=True) course_settings = make_course_settings(course, user) uses_bootstrap = USE_BOOTSTRAP_FLAG.is_enabled() return { 'csrf': csrf(request)['csrf_token'], 'course': course, 'user': user, 'user_info': user_info, 'staff_access': bool(has_access(user, 'staff', course)), 'roles': utils.get_role_ids(course_key), 'can_create_comment': has_permission(user, "create_comment", course.id), 'can_create_subcomment': has_permission(user, "create_sub_comment", course.id), 'can_create_thread': has_permission(user, "create_thread", course.id), 'flag_moderator': bool( has_permission(user, 'openclose_thread', course.id) or has_access(user, 'staff', course) ), 'course_settings': course_settings, 'disable_courseware_js': True, 'uses_bootstrap': uses_bootstrap, 'uses_pattern_library': not uses_bootstrap, }
def test_module_render_with_jump_to_id(self): """ This test validates that the /jump_to_id/<id> shorthand for intracourse linking works assertIn expected. Note there's a HTML element in the 'toy' course with the url_name 'toyjumpto' which defines this linkage """ mock_request = MagicMock() mock_request.user = self.mock_user course = get_course_with_access(self.mock_user, "load", self.course_key) field_data_cache = FieldDataCache.cache_for_descriptor_descendents( self.course_key, self.mock_user, course, depth=2 ) module = render.get_module( self.mock_user, mock_request, self.course_key.make_usage_key("html", "toyjumpto"), field_data_cache ) # get the rendered HTML output which should have the rewritten link html = module.render(STUDENT_VIEW).content # See if the url got rewritten to the target link # note if the URL mapping changes then this assertion will break self.assertIn("/courses/" + self.course_key.to_deprecated_string() + "/jump_to_id/vertical_test", html)
def update_thread(request, course_id, thread_id): """ Given a course id and thread id, update a existing thread, used for both static and ajax submissions """ if 'title' not in request.POST or not request.POST['title'].strip(): return JsonError(_("Title can't be empty")) if 'body' not in request.POST or not request.POST['body'].strip(): return JsonError(_("Body can't be empty")) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) thread = cc.Thread.find(thread_id) thread.body = request.POST["body"] thread.title = request.POST["title"] # The following checks should avoid issues we've seen during deploys, where end users are hitting an updated server # while their browser still has the old client code. This will avoid erasing present values in those cases. if "thread_type" in request.POST: thread.thread_type = request.POST["thread_type"] if "commentable_id" in request.POST: course = get_course_with_access(request.user, 'load', course_key) commentable_ids = get_discussion_categories_ids(course) if request.POST.get("commentable_id") in commentable_ids: thread.commentable_id = request.POST["commentable_id"] else: return JsonError(_("Topic doesn't exist")) thread.save() if request.is_ajax(): return ajax_content_response(request, course_key, thread.to_dict()) else: return JsonResponse(prepare_content(thread.to_dict(), course_key))
def peer_grading(request, course_id): ''' When a student clicks on the "peer grading" button in the open ended interface, link them to a peer grading xmodule in the course. ''' #Get the current course course = get_course_with_access(request.user, course_id, 'load') found_module, problem_url = find_peer_grading_module(course) if not found_module: #This is a student_facing_error error_message = """ Error with initializing peer grading. There has not been a peer grading module created in the courseware that would allow you to grade others. Please check back later for this. """ #This is a dev_facing_error log.exception(error_message + "Current course is: {0}".format(course_id)) return HttpResponse(error_message) return HttpResponseRedirect(problem_url)
def render_to_fragment(self, request, course_id=None, **kwargs): """ Renders the latest update message fragment for the specified course. Returns: A fragment, or None if there is no latest update message. """ course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) ordered_updates = get_ordered_updates(request, course) if not ordered_updates: return None context = { 'ordered_updates': ordered_updates, } html = render_to_string( 'course_experience/course-home-updates-fragment.html', context) return Fragment(html)
def forum_form_discussion(request, course_key): """ Renders the main Discussion page, potentially filtered by a search query """ nr_transaction = newrelic.agent.current_transaction() course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) if request.is_ajax(): user = cc.User.from_django_user(request.user) user_info = user.to_dict() try: unsafethreads, query_params = get_threads(request, course, user_info) # This might process a search query is_staff = has_permission(request.user, 'openclose_thread', course.id) threads = [utils.prepare_content(thread, course_key, is_staff) for thread in unsafethreads] except cc.utils.CommentClientMaintenanceError: return HttpResponseServerError('Forum is in maintenance mode', status=status.HTTP_503_SERVICE_UNAVAILABLE) except ValueError: return HttpResponseServerError("Invalid group_id") with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info) with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context(threads, course, request.user) return utils.JsonResponse({ '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'], 'corrected_text': query_params['corrected_text'], }) else: course_id = unicode(course.id) tab_view = CourseTabView() return tab_view.get(request, course_id, 'discussion')
def _create_comment(request, course_id, thread_id=None, parent_id=None): """ given a course_id, thread_id, and parent_id, create a comment, called from create_comment to do the actual creation """ post = request.POST if 'body' not in post or not post['body'].strip(): return JsonError(_("Body can't be empty")) course = get_course_with_access(request.user, course_id, 'load') if course.allow_anonymous: anonymous = post.get('anonymous', 'false').lower() == 'true' else: anonymous = False if course.allow_anonymous_to_peers: anonymous_to_peers = post.get('anonymous_to_peers', 'false').lower() == 'true' else: anonymous_to_peers = False comment = cc.Comment(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, body=post["body"]) comment.save() if post.get('auto_subscribe', 'false').lower() == 'true': user = cc.User.from_django_user(request.user) user.follow(comment.thread) if request.is_ajax(): return ajax_content_response(request, course_id, comment.to_dict()) else: return JsonResponse(utils.safe_content(comment.to_dict()))
def _create_comment(request, course_id, thread_id=None, parent_id=None): """ given a course_id, thread_id, and parent_id, create a comment, called from create_comment to do the actual creation """ post = request.POST comment = cc.Comment(**extract(post, ['body'])) course = get_course_with_access(request.user, course_id, 'load') if course.allow_anonymous: anonymous = post.get('anonymous', 'false').lower() == 'true' else: anonymous = False if course.allow_anonymous_to_peers: anonymous_to_peers = post.get('anonymous_to_peers', 'false').lower() == 'true' else: 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, }) comment.save() if post.get('auto_subscribe', 'false').lower() == 'true': user = cc.User.from_django_user(request.user) user.follow(comment.thread) if request.is_ajax(): return ajax_content_response(request, course_id, comment.to_dict()) else: return JsonResponse(utils.safe_content(comment.to_dict()))
def spoc_gradebook(request, course_id): """ Show the gradebook for this course: - Only shown for courses with enrollment < settings.FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS") - Only displayed to course staff """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(request.user, 'staff', course_key, depth=None) enrolled_students = User.objects.filter( courseenrollment__course_id=course_key, courseenrollment__is_active=1 ).order_by('username').select_related("profile") # TODO (vshnayder): implement pagination to show to large courses max_num_students = settings.FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS") enrolled_students = enrolled_students[:max_num_students] # HACK! student_info = [ { 'username': student.username, 'id': student.id, 'email': student.email, 'grade_summary': student_grades(student, request, course), 'realname': student.profile.name, } for student in enrolled_students ] return render_to_response('courseware/gradebook.html', { 'students': student_info, 'course': course, 'course_id': course_key, # Checked above 'staff_access': True, 'ordered_grades': sorted(course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True), })
def update_thread(request, course_id, thread_id): """ Given a course id and thread id, update a existing thread, used for both static and ajax submissions """ if 'title' not in request.POST or not request.POST['title'].strip(): return JsonError(_("Title can't be empty")) if 'body' not in request.POST or not request.POST['body'].strip(): return JsonError(_("Body can't be empty")) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) thread = cc.Thread.find(thread_id) # Get thread context first in order to be safe from reseting the values of thread object later thread_context = getattr(thread, "context", "course") thread.body = request.POST["body"] thread.title = request.POST["title"] user = request.user # The following checks should avoid issues we've seen during deploys, where end users are hitting an updated server # while their browser still has the old client code. This will avoid erasing present values in those cases. if "thread_type" in request.POST: thread.thread_type = request.POST["thread_type"] if "commentable_id" in request.POST: commentable_id = request.POST["commentable_id"] course = get_course_with_access(user, 'load', course_key) if thread_context == "course" and not discussion_category_id_access( course, user, commentable_id): return JsonError(_("Topic doesn't exist")) else: thread.commentable_id = commentable_id thread.save() thread_edited.send(sender=None, user=user, post=thread) if request.is_ajax(): return ajax_content_response(request, course_key, thread.to_dict()) else: return JsonResponse(prepare_content(thread.to_dict(), course_key))
def render_to_fragment(self, request, course_id, user_access, **kwargs): """ Renders a course message fragment for the specified course. """ course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, 'load', course_key) # Get time until the start date, if already started, or no start date, value will be zero or negative now = datetime.now(UTC()) already_started = course.start and now > course.start days_until_start_string = "started" if already_started else format_timedelta(course.start - now, locale=to_locale(get_language())) course_start_data = { 'course_start_date': format_date(course.start, locale=to_locale(get_language())), 'already_started': already_started, 'days_until_start_string': days_until_start_string } # Register the course home messages to be loaded on the page self.register_course_home_messages(request, course, user_access, course_start_data) # Grab the relevant messages course_home_messages = list(CourseHomeMessages.user_messages(request)) # Return None if user is enrolled and course has begun if user_access['is_enrolled'] and already_started: return None # Grab the logo image_src = "course_experience/images/home_message_author.png" context = { 'course_home_messages': course_home_messages, 'image_src': image_src, } html = render_to_string('course_experience/course-messages-fragment.html', context) return Fragment(html)
def context_processor(request): """ This is a context processor which looks at the URL while we are in the wiki. If the url is in the form /courses/(course_id)/wiki/... 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, '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 {}
def index(request, course_id, book_index, page=None): course = get_course_with_access(request.user, course_id, 'load') staff_access = has_access(request.user, course, 'staff') book_index = int(book_index) if book_index < 0 or book_index >= len(course.textbooks): raise Http404("Invalid book index value: {0}".format(book_index)) textbook = course.textbooks[book_index] table_of_contents = textbook.table_of_contents if page is None: page = textbook.start_page return render_to_response( 'staticbook.html', { '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, 'staff_access': staff_access })
def get_all_last_access(request, course_id): """ Show the last_access for the users enrolled in this course: Only displayed to course staff """ course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, 'staff', course_key, depth=None) enrollments_info, page = get_last_access_user_page(request, course_key) # student_info, page = get_grade_book_page(request, course, course_key) return render_to_response( 'courseware/last_access.html', { 'page': page, 'page_url': reverse('last_access', kwargs={'course_id': unicode(course_key)}), 'course': course, 'enrollments': enrollments_info })
def _get_course(course_key, user): """ Get the course descriptor, raising CourseNotFoundError if the course is not found or the user cannot access forums for the course, and DiscussionDisabledError if the discussion tab is disabled for the course. """ try: course = get_course_with_access(user, 'load', course_key, check_if_enrolled=True) except Http404: # Convert 404s into CourseNotFoundErrors. raise CourseNotFoundError("Course not found.") except CourseAccessRedirect: # Raise course not found if the user cannot access the course # since it doesn't make sense to redirect an API. raise CourseNotFoundError("Course not found.") if not any([ tab.type == 'discussion' and tab.is_enabled(course, user) for tab in course.tabs ]): raise DiscussionDisabledError("Discussion is disabled for the course.") return course
def static_tab(request, course_id, tab_slug): """ Display the courses tab with the given name. Assumes the course_id is in a valid format. """ course = get_course_with_access(request.user, course_id, 'load') tab = tabs.get_static_tab_by_slug(course, tab_slug) if tab is None: raise Http404 contents = tabs.get_static_tab_contents(request, course, tab) if contents is None: raise Http404 staff_access = has_access(request.user, course, 'staff') return render_to_response( 'courseware/static_tab.html', { 'course': course, 'tab': tab, 'tab_contents': contents, 'staff_access': staff_access, })
def mktg_course_about(request, course_id): """ This is the button that gets put into an iframe on the Drupal site """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) try: course = get_course_with_access(request.user, 'see_exists', course_key) except (ValueError, Http404) as e: # if a course does not exist yet, display a coming # soon button return render_to_response( 'courseware/mktg_coming_soon.html', {'course_id': course_key.to_deprecated_string()} ) registered = registered_for_course(course, request.user) if has_access(request.user, 'load', course): course_target = reverse('info', args=[course.id.to_deprecated_string()]) else: course_target = reverse('about_course', args=[course.id.to_deprecated_string()]) allow_registration = has_access(request.user, 'enroll', course) show_courseware_link = (has_access(request.user, 'load', course) or settings.FEATURES.get('ENABLE_LMS_MIGRATION')) course_modes = CourseMode.modes_for_course(course.id) return render_to_response('courseware/mktg_course_about.html', { 'course': course, 'registered': registered, 'allow_registration': allow_registration, 'course_target': course_target, 'show_courseware_link': show_courseware_link, 'course_modes': course_modes, })
def static_tab(request, course_id, tab_slug): """ Display the courses tab with the given name. Assumes the course_id is in a valid format. """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(request.user, 'load', course_key) tab = CourseTabList.get_tab_by_slug(course.tabs, tab_slug) if tab is None: raise Http404 contents = get_static_tab_contents(request, course, tab) if contents is None: raise Http404 return render_to_response('courseware/static_tab.html', { 'course': course, 'tab': tab, 'tab_contents': contents, })
def edxnotes_visibility(request, course_id): """ Handle ajax call from "Show notes" checkbox. """ course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, "load", course_key) field_data_cache = FieldDataCache([course], course_key, request.user) course_module = get_module_for_descriptor( request.user, request, course, field_data_cache, course_key, course=course ) if not is_feature_enabled(course): raise Http404 try: visibility = json.loads(request.body)["visibility"] course_module.edxnotes_visibility = visibility course_module.save() return JsonResponse(status=200) except (ValueError, KeyError): log.warning( "Could not decode request body as JSON and find a boolean visibility field: '%s'", request.body ) return JsonResponseBadRequest()
def calendar_dashboard(request, course_id): course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, "load", course_key) add_lookup( 'main', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'calendar_tab/templates')) csrf_token = csrf(request)['csrf_token'] tab = CourseTabList.get_tab_by_id(course.tabs, "calendar_tab") is_staff = bool(has_access(request.user, 'staff', course)) try: data = json.loads(tab.data) except (TypeError, ValueError): data = {} context = { "course": course, "csrf_token": csrf_token, 'url': data.get('url', '#'), 'message': data.get('message', _('Open calendar')), 'is_staff': is_staff } return render_to_response("calendar_tab/calendar_tab.html", context)
def render_to_fragment(self, request, course_id=None, **kwargs): """ Renders the user's course bookmarks as a fragment. """ course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) context = { 'csrf': csrf(request)['csrf_token'], 'course': course, 'bookmarks_api_url': reverse('bookmarks'), 'language_preference': 'en', # TODO: } html = render_to_string( 'course_bookmarks/course-bookmarks-fragment.html', context) inline_js = render_to_string( 'course_bookmarks/course_bookmarks_js.template', context) fragment = Fragment(html) self.add_fragment_resource_urls(fragment) fragment.add_javascript(inline_js) return fragment
def course_info(request, course_id): """ Display the course's info.html, or 404 if there is no such course. Assumes the course_id is in a valid format. """ course = get_course_with_access(request.user, course_id, 'load') staff_access = has_access(request.user, course, 'staff') masq = setup_masquerade(request, staff_access) # allow staff to toggle masquerade on info page studio_url = get_studio_url(course_id, 'course_info') reverifications = fetch_reverify_banner_info(request, course_id) context = { 'request': request, 'course_id': course_id, 'cache': None, 'course': course, 'staff_access': staff_access, 'masquerade': masq, 'studio_url': studio_url, 'reverifications': reverifications, } return render_to_response('courseware/info.html', context)
def render_to_fragment(self, request, course_id=None, **kwargs): """ Renders the course's home page as a fragment. """ course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) # Render the outline as a fragment outline_fragment = CourseOutlineFragmentView().render_to_fragment(request, course_id=course_id, **kwargs) # Get the last accessed courseware last_accessed_url, __ = get_last_accessed_courseware(course, request, request.user) # Render the course home fragment context = { 'csrf': csrf(request)['csrf_token'], 'course': course, 'outline_fragment': outline_fragment, 'has_visited_course': last_accessed_url is not None, 'disable_courseware_js': True, 'uses_pattern_library': True, } html = render_to_string('course_experience/course-home-fragment.html', context) return Fragment(html)
def course_survey(request, course_id): """ URL endpoint to present a survey that is associated with a course_id Note that the actual implementation of course survey is handled in the views.py file in the Survey Djangoapp """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(request.user, 'load', course_key) redirect_url = reverse('info', args=[course_id]) # if there is no Survey associated with this course, # then redirect to the course instead if not course.course_survey_name: return redirect(redirect_url) return survey.views.view_student_survey( request.user, course.course_survey_name, course=course, redirect_url=redirect_url, is_required=course.course_survey_required, )
def mktg_course_about(request, course_id): """This is the button that gets put into an iframe on the Drupal site.""" course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) try: permission_name = microsite.get_value( 'COURSE_ABOUT_VISIBILITY_PERMISSION', settings.COURSE_ABOUT_VISIBILITY_PERMISSION ) course = get_course_with_access(request.user, permission_name, course_key) except (ValueError, Http404): # If a course does not exist yet, display a "Coming Soon" button return render_to_response( 'courseware/mktg_coming_soon.html', {'course_id': course_key.to_deprecated_string()} ) registered = registered_for_course(course, request.user) if has_access(request.user, 'load', course): course_target = reverse('info', args=[course.id.to_deprecated_string()]) else: course_target = reverse('about_course', args=[course.id.to_deprecated_string()]) allow_registration = has_access(request.user, 'enroll', course) show_courseware_link = (has_access(request.user, 'load', course) or settings.FEATURES.get('ENABLE_LMS_MIGRATION')) course_modes = CourseMode.modes_for_course_dict(course.id) context = { 'course': course, 'registered': registered, 'allow_registration': allow_registration, 'course_target': course_target, 'show_courseware_link': show_courseware_link, 'course_modes': course_modes, } if settings.FEATURES.get('ENABLE_MKTG_EMAIL_OPT_IN'): # Drupal will pass organization names using a GET parameter, as follows: # ?org=Harvard # ?org=Harvard,MIT # If no full names are provided, the marketing iframe won't show the # email opt-in checkbox. org = request.GET.get('org') if org: org_list = org.split(',') # HTML-escape the provided organization names org_list = [cgi.escape(org) for org in org_list] if len(org_list) > 1: if len(org_list) > 2: # Translators: The join of three or more institution names (e.g., Harvard, MIT, and Dartmouth). org_name_string = _("{first_institutions}, and {last_institution}").format( first_institutions=u", ".join(org_list[:-1]), last_institution=org_list[-1] ) else: # Translators: The join of two institution names (e.g., Harvard and MIT). org_name_string = _("{first_institution} and {second_institution}").format( first_institution=org_list[0], second_institution=org_list[1] ) else: org_name_string = org_list[0] context['checkbox_label'] = ungettext( "I would like to receive email from {institution_series} and learn about its other programs.", "I would like to receive email from {institution_series} and learn about their other programs.", len(org_list) ).format(institution_series=org_name_string) # The edx.org marketing site currently displays only in English. # To avoid displaying a different language in the register / access button, # we force the language to English. # However, OpenEdX installations with a different marketing front-end # may want to respect the language specified by the user or the site settings. force_english = settings.FEATURES.get('IS_EDX_DOMAIN', False) if force_english: translation.activate('en-us') try: return render_to_response('courseware/mktg_course_about.html', context) finally: # Just to be safe, reset the language if we forced it to be English. if force_english: translation.deactivate()
def course_about(request, course_id): """ Display the course's about page. Assumes the course_id is in a valid format. """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) with modulestore().bulk_operations(course_key): permission_name = microsite.get_value( 'COURSE_ABOUT_VISIBILITY_PERMISSION', settings.COURSE_ABOUT_VISIBILITY_PERMISSION ) course = get_course_with_access(request.user, permission_name, course_key) if microsite.get_value('ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False)): return redirect(reverse('info', args=[course.id.to_deprecated_string()])) registered = registered_for_course(course, request.user) staff_access = has_access(request.user, 'staff', course) studio_url = get_studio_url(course, 'settings/details') if has_access(request.user, 'load', course): course_target = reverse('info', args=[course.id.to_deprecated_string()]) else: course_target = reverse('about_course', args=[course.id.to_deprecated_string()]) show_courseware_link = ( ( has_access(request.user, 'load', course) and has_access(request.user, 'view_courseware_with_prerequisites', course) ) or settings.FEATURES.get('ENABLE_LMS_MIGRATION') ) # Note: this is a flow for payment for course registration, not the Verified Certificate flow. registration_price = 0 in_cart = False reg_then_add_to_cart_link = "" _is_shopping_cart_enabled = is_shopping_cart_enabled() if _is_shopping_cart_enabled: registration_price = CourseMode.min_course_price_for_currency(course_key, settings.PAID_COURSE_REGISTRATION_CURRENCY[0]) if request.user.is_authenticated(): cart = shoppingcart.models.Order.get_cart_for_user(request.user) in_cart = shoppingcart.models.PaidCourseRegistration.contained_in_order(cart, course_key) or \ shoppingcart.models.CourseRegCodeItem.contained_in_order(cart, course_key) reg_then_add_to_cart_link = "{reg_url}?course_id={course_id}&enrollment_action=add_to_cart".format( reg_url=reverse('register_user'), course_id=course.id.to_deprecated_string()) course_price = get_cosmetic_display_price(course, registration_price) can_add_course_to_cart = _is_shopping_cart_enabled and registration_price # Used to provide context to message to student if enrollment not allowed can_enroll = has_access(request.user, 'enroll', course) invitation_only = course.invitation_only is_course_full = CourseEnrollment.is_course_full(course) # Register button should be disabled if one of the following is true: # - Student is already registered for course # - Course is already full # - Student cannot enroll in course active_reg_button = not(registered or is_course_full or not can_enroll) is_shib_course = uses_shib(course) # get prerequisite courses display names pre_requisite_courses = get_prerequisite_courses_display(course) return render_to_response('courseware/course_about.html', { 'course': course, 'staff_access': staff_access, 'studio_url': studio_url, 'registered': registered, 'course_target': course_target, 'is_cosmetic_price_enabled': settings.FEATURES.get('ENABLE_COSMETIC_DISPLAY_PRICE'), 'course_price': course_price, 'in_cart': in_cart, 'reg_then_add_to_cart_link': reg_then_add_to_cart_link, 'show_courseware_link': show_courseware_link, 'is_course_full': is_course_full, 'can_enroll': can_enroll, 'invitation_only': invitation_only, 'active_reg_button': active_reg_button, 'is_shib_course': is_shib_course, # We do not want to display the internal courseware header, which is used when the course is found in the # context. This value is therefor explicitly set to render the appropriate header. 'disable_courseware_header': True, 'can_add_course_to_cart': can_add_course_to_cart, 'cart_link': reverse('shoppingcart.views.show_cart'), 'pre_requisite_courses': pre_requisite_courses })
def _index_bulk_op(request, course_key, chapter, section, position): """ Render the index page for the specified course. """ user = request.user course = get_course_with_access(user, 'load', course_key, depth=2) staff_access = has_access(user, 'staff', course) registered = registered_for_course(course, user) if not registered: # TODO (vshnayder): do course instructors need to be registered to see course? log.debug(u'User %s tried to view course %s but is not enrolled', user, course.location.to_deprecated_string()) return redirect(reverse('about_course', args=[course_key.to_deprecated_string()])) # see if all pre-requisites (as per the milestones app feature) have been fulfilled # Note that if the pre-requisite feature flag has been turned off (default) then this check will # always pass if not has_access(user, 'view_courseware_with_prerequisites', course): # prerequisites have not been fulfilled therefore redirect to the Dashboard log.info( u'User %d tried to view course %s ' u'without fulfilling prerequisites', user.id, unicode(course.id)) return redirect(reverse('dashboard')) # check to see if there is a required survey that must be taken before # the user can access the course. if survey.utils.must_answer_survey(course, user): return redirect(reverse('course_survey', args=[unicode(course.id)])) masquerade = setup_masquerade(request, course_key, staff_access) try: field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course_key, user, course, depth=2) course_module = get_module_for_descriptor(user, request, course, field_data_cache, course_key) if course_module is None: log.warning(u'If you see this, something went wrong: if we got this' u' far, should have gotten a course module for this user') return redirect(reverse('about_course', args=[course_key.to_deprecated_string()])) studio_url = get_studio_url(course, 'course') context = { 'csrf': csrf(request)['csrf_token'], 'accordion': render_accordion(request, course, chapter, section, field_data_cache), 'COURSE_TITLE': course.display_name_with_default, 'course': course, 'init': '', 'fragment': Fragment(), 'staff_access': staff_access, 'studio_url': studio_url, 'masquerade': masquerade, 'xqa_server': settings.FEATURES.get('USE_XQA_SERVER', 'http://*****:*****@content-qa.mitx.mit.edu/xqa'), 'reverifications': fetch_reverify_banner_info(request, course_key), } now = datetime.now(UTC()) effective_start = _adjust_start_date_for_beta_testers(user, course, course_key) if staff_access and now < effective_start: # Disable student view button if user is staff and # course is not yet visible to students. context['disable_student_access'] = True has_content = course.has_children_at_depth(CONTENT_DEPTH) if not has_content: # Show empty courseware for a course with no units return render_to_response('courseware/courseware.html', context) elif chapter is None: # passing CONTENT_DEPTH avoids returning 404 for a course with an # empty first section and a second section with content return redirect_to_course_position(course_module, CONTENT_DEPTH) # Only show the chat if it's enabled by the course and in the # settings. show_chat = course.show_chat and settings.FEATURES['ENABLE_CHAT'] if show_chat: context['chat'] = chat_settings(course, user) # If we couldn't load the chat settings, then don't show # the widget in the courseware. if context['chat'] is None: show_chat = False context['show_chat'] = show_chat chapter_descriptor = course.get_child_by(lambda m: m.location.name == chapter) if chapter_descriptor is not None: save_child_position(course_module, chapter) else: raise Http404('No chapter descriptor found with name {}'.format(chapter)) chapter_module = course_module.get_child_by(lambda m: m.location.name == chapter) if chapter_module is None: # User may be trying to access a chapter that isn't live yet if masquerade and masquerade.role == 'student': # if staff is masquerading as student be kinder, don't 404 log.debug('staff masquerading as student: no chapter %s', chapter) return redirect(reverse('courseware', args=[course.id.to_deprecated_string()])) raise Http404 if section is not None: section_descriptor = chapter_descriptor.get_child_by(lambda m: m.location.name == section) if section_descriptor is None: # Specifically asked-for section doesn't exist if masquerade and masquerade.role == 'student': # don't 404 if staff is masquerading as student log.debug('staff masquerading as student: no section %s', section) return redirect(reverse('courseware', args=[course.id.to_deprecated_string()])) raise Http404 ## Allow chromeless operation if section_descriptor.chrome: chrome = [s.strip() for s in section_descriptor.chrome.lower().split(",")] if 'accordion' not in chrome: context['disable_accordion'] = True if 'tabs' not in chrome: context['disable_tabs'] = True if section_descriptor.default_tab: context['default_tab'] = section_descriptor.default_tab # cdodge: this looks silly, but let's refetch the section_descriptor with depth=None # which will prefetch the children more efficiently than doing a recursive load section_descriptor = modulestore().get_item(section_descriptor.location, depth=None) # Load all descendants of the section, because we're going to display its # html, which in general will need all of its children section_field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course_key, user, section_descriptor, depth=None, asides=XBlockAsidesConfig.possible_asides() ) # Verify that position a string is in fact an int if position is not None: try: int(position) except ValueError: raise Http404("Position {} is not an integer!".format(position)) section_module = get_module_for_descriptor( request.user, request, section_descriptor, section_field_data_cache, course_key, position ) if section_module is None: # User may be trying to be clever and access something # they don't have access to. raise Http404 # Save where we are in the chapter save_child_position(chapter_module, section) context['fragment'] = section_module.render(STUDENT_VIEW) context['section_title'] = section_descriptor.display_name_with_default else: # section is none, so display a message studio_url = get_studio_url(course, 'course') prev_section = get_current_child(chapter_module) if prev_section is None: # Something went wrong -- perhaps this chapter has no sections visible to the user. # Clearing out the last-visited state and showing "first-time" view by redirecting # to courseware. course_module.position = None course_module.save() return redirect(reverse('courseware', args=[course.id.to_deprecated_string()])) prev_section_url = reverse('courseware_section', kwargs={ 'course_id': course_key.to_deprecated_string(), 'chapter': chapter_descriptor.url_name, 'section': prev_section.url_name }) context['fragment'] = Fragment(content=render_to_string( 'courseware/welcome-back.html', { 'course': course, 'studio_url': studio_url, 'chapter_module': chapter_module, 'prev_section': prev_section, 'prev_section_url': prev_section_url } )) result = render_to_response('courseware/courseware.html', context) except Exception as e: # Doesn't bar Unicode characters from URL, but if Unicode characters do # cause an error it is a graceful failure. if isinstance(e, UnicodeEncodeError): raise Http404("URL contains Unicode characters") if isinstance(e, Http404): # let it propagate raise # In production, don't want to let a 500 out for any reason if settings.DEBUG: raise else: log.exception( u"Error in index view: user={user}, course={course}, chapter={chapter}" u" section={section} position={position}".format( user=user, course=course, chapter=chapter, section=section, position=position )) try: result = render_to_response('courseware/courseware-error.html', { 'staff_access': staff_access, 'course': course }) except: # Let the exception propagate, relying on global config to at # at least return a nice error message log.exception("Error while rendering courseware-error page") raise return result
def course_discussions_settings_handler(request, course_key_string): """ The restful handler for divided discussion setting requests. Requires JSON. This will raise 404 if user is not staff. GET Returns the JSON representation of divided discussion settings for the course. PATCH Updates the divided discussion settings for the course. Returns the JSON representation of updated settings. """ course_key = CourseKey.from_string(course_key_string) course = get_course_with_access(request.user, 'staff', course_key) discussion_settings = get_course_discussion_settings(course_key) if request.method == 'PATCH': divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions( course, discussion_settings) settings_to_change = {} if 'divided_course_wide_discussions' in request.json or 'divided_inline_discussions' in request.json: divided_course_wide_discussions = request.json.get( 'divided_course_wide_discussions', divided_course_wide_discussions) divided_inline_discussions = request.json.get( 'divided_inline_discussions', divided_inline_discussions) settings_to_change[ 'divided_discussions'] = divided_course_wide_discussions + divided_inline_discussions if 'always_divide_inline_discussions' in request.json: settings_to_change[ 'always_divide_inline_discussions'] = request.json.get( 'always_divide_inline_discussions') if 'division_scheme' in request.json: settings_to_change['division_scheme'] = request.json.get( 'division_scheme') if not settings_to_change: return JsonResponse({"error": unicode("Bad Request")}, 400) try: if settings_to_change: discussion_settings = set_course_discussion_settings( course_key, **settings_to_change) except ValueError as err: # Note: error message not translated because it is not exposed to the user (UI prevents this state). return JsonResponse({"error": unicode(err)}, 400) divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions( course, discussion_settings) return JsonResponse({ 'id': discussion_settings.id, 'divided_inline_discussions': divided_inline_discussions, 'divided_course_wide_discussions': divided_course_wide_discussions, 'always_divide_inline_discussions': discussion_settings.always_divide_inline_discussions, 'division_scheme': discussion_settings.division_scheme, 'available_division_schemes': available_division_schemes(course_key) })
def discussion_topics(request, course_key_string): """ The handler for divided discussion categories requests. This will raise 404 if user is not staff. Returns the JSON representation of discussion topics w.r.t categories for the course. Example: >>> example = { >>> "course_wide_discussions": { >>> "entries": { >>> "General": { >>> "sort_key": "General", >>> "is_divided": True, >>> "id": "i4x-edx-eiorguegnru-course-foobarbaz" >>> } >>> } >>> "children": ["General", "entry"] >>> }, >>> "inline_discussions" : { >>> "subcategories": { >>> "Getting Started": { >>> "subcategories": {}, >>> "children": [ >>> ["Working with Videos", "entry"], >>> ["Videos on edX", "entry"] >>> ], >>> "entries": { >>> "Working with Videos": { >>> "sort_key": None, >>> "is_divided": False, >>> "id": "d9f970a42067413cbb633f81cfb12604" >>> }, >>> "Videos on edX": { >>> "sort_key": None, >>> "is_divided": False, >>> "id": "98d8feb5971041a085512ae22b398613" >>> } >>> } >>> }, >>> "children": ["Getting Started", "subcategory"] >>> }, >>> } >>> } """ course_key = CourseKey.from_string(course_key_string) course = get_course_with_access(request.user, 'staff', course_key) discussion_topics = {} discussion_category_map = utils.get_discussion_category_map( course, request.user, divided_only_if_explicit=True, exclude_unstarted=False) # We extract the data for the course wide discussions from the category map. course_wide_entries = discussion_category_map.pop('entries') course_wide_children = [] inline_children = [] for name, c_type in discussion_category_map['children']: if name in course_wide_entries and c_type == TYPE_ENTRY: course_wide_children.append([name, c_type]) else: inline_children.append([name, c_type]) discussion_topics['course_wide_discussions'] = { 'entries': course_wide_entries, 'children': course_wide_children } discussion_category_map['children'] = inline_children discussion_topics['inline_discussions'] = discussion_category_map return JsonResponse(discussion_topics)
def followed_threads(request, course_key, user_id): """ Ajax-only endpoint retrieving the threads followed by a specific user. """ course = get_course_with_access(request.user, 'load_forum', course_key, check_if_enrolled=True) try: profiled_user = cc.User(id=user_id, course_id=course_key) default_query_params = { 'page': 1, 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities 'sort_key': 'date', } query_params = merge_dict( default_query_params, strip_none( extract(request.GET, [ 'page', 'sort_key', 'flagged', 'unread', 'unanswered', ]))) try: group_id = get_group_id_for_comments_service(request, course_key) except ValueError: return HttpResponseServerError("Invalid group_id") if group_id is not None: query_params['group_id'] = group_id paginated_results = profiled_user.subscribed_threads(query_params) print "\n \n \n paginated results \n \n \n " print paginated_results query_params['page'] = paginated_results.page query_params['num_pages'] = paginated_results.num_pages user_info = cc.User.from_django_user(request.user).to_dict() with newrelic_function_trace("get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads( course_key, paginated_results.collection, request.user, user_info) if request.is_ajax(): is_staff = has_permission(request.user, 'openclose_thread', course.id) return utils.JsonResponse({ 'annotated_content_info': annotated_content_info, 'discussion_data': [ utils.prepare_content(thread, course_key, is_staff) for thread in paginated_results.collection ], 'page': query_params['page'], 'num_pages': query_params['num_pages'], }) #TODO remove non-AJAX support, it does not appear to be used and does not appear to work. else: context = { 'course': course, 'user': request.user, 'django_user': User.objects.get(id=user_id), 'profiled_user': profiled_user.to_dict(), 'threads': paginated_results.collection, 'user_info': user_info, 'annotated_content_info': annotated_content_info, # 'content': content, } return render_to_response('discussion/user_profile.html', context) except User.DoesNotExist: raise Http404
def user_profile(request, course_key, user_id): """ Renders a response to display the user profile page (shown after clicking on a post author's username). """ user = cc.User.from_django_user(request.user) course = get_course_with_access(request.user, 'load_forum', course_key, check_if_enrolled=True) try: # If user is not enrolled in the course, do not proceed. django_user = User.objects.get(id=user_id) if not CourseEnrollment.is_enrolled(django_user, course.id): raise Http404 query_params = { 'page': request.GET.get('page', 1), 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities } try: group_id = get_group_id_for_comments_service(request, course_key) except ValueError: return HttpResponseServerError("Invalid group_id") if group_id is not None: query_params['group_id'] = group_id profiled_user = cc.User(id=user_id, course_id=course_key, group_id=group_id) else: profiled_user = cc.User(id=user_id, course_id=course_key) threads, page, num_pages = profiled_user.active_threads(query_params) query_params['page'] = page query_params['num_pages'] = num_pages with newrelic_function_trace("get_metadata_for_threads"): user_info = cc.User.from_django_user(request.user).to_dict() annotated_content_info = utils.get_metadata_for_threads( course_key, threads, request.user, user_info) is_staff = has_permission(request.user, 'openclose_thread', course.id) threads = [ utils.prepare_content(thread, course_key, is_staff) for thread in threads ] with newrelic_function_trace("add_courseware_context"): add_courseware_context(threads, course, request.user) if request.is_ajax(): return utils.JsonResponse({ 'discussion_data': threads, 'page': query_params['page'], 'num_pages': query_params['num_pages'], 'annotated_content_info': annotated_content_info, }) else: user_roles = django_user.roles.filter( course_id=course.id).order_by("name").values_list( "name", flat=True).distinct() with newrelic_function_trace("get_cohort_info"): course_discussion_settings = get_course_discussion_settings( course_key) user_group_id = get_group_id_for_user( request.user, course_discussion_settings) context = _create_base_discussion_view_context(request, course_key) context.update({ 'django_user': django_user, 'django_user_roles': user_roles, 'profiled_user': profiled_user.to_dict(), 'threads': threads, 'user_group_id': user_group_id, 'annotated_content_info': annotated_content_info, 'page': query_params['page'], 'num_pages': query_params['num_pages'], 'sort_preference': user.default_sort_key, 'learner_profile_page_url': reverse('learner_profile', kwargs={'username': django_user.username}), }) return render_to_response( 'discussion/discussion_profile_page.html', context) except User.DoesNotExist: raise Http404
def create_user_profile_context(request, course_key, user_id): """ Generate a context dictionary for the user profile. """ user = cc.User.from_django_user(request.user) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) # If user is not enrolled in the course, do not proceed. django_user = User.objects.get(id=user_id) if not CourseEnrollment.is_enrolled(django_user, course.id): raise Http404 query_params = { 'page': request.GET.get('page', 1), 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities } group_id = get_group_id_for_comments_service(request, course_key) if group_id is not None: query_params['group_id'] = group_id profiled_user = cc.User(id=user_id, course_id=course_key, group_id=group_id) else: profiled_user = cc.User(id=user_id, course_id=course_key) threads, page, num_pages = profiled_user.active_threads(query_params) query_params['page'] = page query_params['num_pages'] = num_pages with function_trace("get_metadata_for_threads"): user_info = cc.User.from_django_user(request.user).to_dict() annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info) is_staff = has_permission(request.user, 'openclose_thread', course.id) threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads] with function_trace("add_courseware_context"): add_courseware_context(threads, course, request.user) # TODO: LEARNER-3854: If we actually implement Learner Analytics code, this # code was original protected to not run in user_profile() if is_ajax(). # Someone should determine if that is still necessary (i.e. was that ever # called as is_ajax()) and clean this up as necessary. user_roles = django_user.roles.filter( course_id=course.id ).order_by("name").values_list("name", flat=True).distinct() with function_trace("get_cohort_info"): course_discussion_settings = get_course_discussion_settings(course_key) user_group_id = get_group_id_for_user(request.user, course_discussion_settings) context = _create_base_discussion_view_context(request, course_key) context.update({ 'django_user': django_user, 'django_user_roles': user_roles, 'profiled_user': profiled_user.to_dict(), 'threads': threads, 'user_group_id': user_group_id, 'annotated_content_info': annotated_content_info, 'page': query_params['page'], 'num_pages': query_params['num_pages'], 'sort_preference': user.default_sort_key, 'learner_profile_page_url': reverse('learner_profile', kwargs={'username': django_user.username}), }) return context
def create_thread(request, course_id, commentable_id): """ Given a course and commentble ID, create the thread """ log.debug("Creating new thread in %r, id %r", course_id, commentable_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(request.user, 'load', course_key) post = request.POST user = request.user if course.allow_anonymous: anonymous = post.get('anonymous', 'false').lower() == 'true' else: anonymous = False if course.allow_anonymous_to_peers: anonymous_to_peers = post.get('anonymous_to_peers', 'false').lower() == 'true' else: anonymous_to_peers = False if 'title' not in post or not post['title'].strip(): return JsonError(_("Title can't be empty")) if 'body' not in post or not post['body'].strip(): return JsonError(_("Body can't be empty")) params = { 'anonymous': anonymous, 'anonymous_to_peers': anonymous_to_peers, 'commentable_id': commentable_id, 'course_id': course_key.to_deprecated_string(), 'user_id': user.id, 'thread_type': post["thread_type"], 'body': post["body"], 'title': post["title"], } # Check for whether this commentable belongs to a team, and add the right context if get_team(commentable_id) is not None: params['context'] = ThreadContext.STANDALONE else: params['context'] = ThreadContext.COURSE thread = cc.Thread(**params) # Cohort the thread if required try: group_id = get_group_id_for_comments_service(request, course_key, commentable_id) except ValueError: return HttpResponseBadRequest("Invalid cohort id") if group_id is not None: thread.group_id = group_id thread.save() thread_created.send(sender=None, user=user, post=thread) # patch for backward compatibility to comments service if 'pinned' not in thread.attributes: thread['pinned'] = False follow = post.get('auto_subscribe', 'false').lower() == 'true' if follow: cc_user = cc.User.from_django_user(user) cc_user.follow(thread) event_data = get_thread_created_event_data(thread, follow) data = thread.to_dict() add_courseware_context([data], course, user) track_forum_event(request, THREAD_CREATED_EVENT_NAME, course, thread, event_data) if request.is_ajax(): return ajax_content_response(request, course_key, data) else: return JsonResponse(prepare_content(data, course_key))