def _listen_for_passing_grade(sender, user, course_id, **kwargs): # pylint: disable=unused-argument """ Listen for a learner passing a course, send cert generation task, downstream signal from COURSE_GRADE_CHANGED """ # No flags enabled if ( not waffle.waffle().is_enabled(waffle.SELF_PACED_ONLY) and not waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY) ): return # Only SELF_PACED_ONLY flag enabled if waffle.waffle().is_enabled(waffle.SELF_PACED_ONLY): if not courses.get_course_by_id(course_key, depth=0).self_paced: return # Only INSTRUCTOR_PACED_ONLY flag enabled elif waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY): if courses.get_course_by_id(course_key, depth=0).self_paced: return if GeneratedCertificate.certificate_for_student(self.user, self.course_id) is None: generate_certificate.apply_async( student=user, course_key=course_id, ) log.info(u'Certificate generation task initiated for {user} : {course} via passing grade'.format( user=user.id, course=course_id ))
def handle(self, *args, **options): print "options = ", options try: course_ids = options['course_id_or_dir'] except KeyError: print self.help return course_key = None # parse out the course id into a coursekey for course_id in course_ids: try: course_key = CourseKey.from_string(course_id) # if it's not a new-style course key, parse it from an old-style # course key except InvalidKeyError: course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) try: get_course_by_id(course_key) except Http404 as err: print "-----------------------------------------------------------------------------" print "Sorry, cannot find course with id {}".format(course_id) print "Got exception {}".format(err) print "Please provide a course ID or course data directory name, eg content-mit-801rq" return print "-----------------------------------------------------------------------------" print "Computing grades for {}".format(course_id) offline_grade_calculation(course_key)
def _listen_for_certificate_whitelist_append(sender, instance, **kwargs): # pylint: disable=unused-argument switches = waffle.waffle() # All flags enabled if ( not switches.is_enabled(waffle.SELF_PACED_ONLY) and not switches.is_enabled(waffle.INSTRUCTOR_PACED_ONLY) ): return # Only SELF_PACED_ONLY flag enabled if not switches.is_enabled(waffle.INSTRUCTOR_PACED_ONLY): if not courses.get_course_by_id(instance.course_id, depth=0).self_paced: return # Only INSTRUCTOR_PACED_ONLY flag enabled if not switches.is_enabled(waffle.SELF_PACED_ONLY): if courses.get_course_by_id(instance.course_id, depth=0).self_paced: return generate_certificate.apply_async( student=instance.user, course_key=instance.course_id, ) log.info(u'Certificate generation task initiated for {user} : {course} via whitelist'.format( user=instance.user.id, course=instance.course_id ))
def register_code_redemption(request, registration_code): """ This view allows the student to redeem the registration code and enroll in the course. """ # Add some rate limiting here by re-using the RateLimitMixin as a helper class site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME) limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Rate limit exceeded in registration code redemption.") return HttpResponseForbidden() template_to_render = 'shoppingcart/registration_code_receipt.html' if request.method == "GET": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) context = { 'reg_code_already_redeemed': reg_code_already_redeemed, 'reg_code_is_valid': reg_code_is_valid, 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'registered_for_course': registered_for_course(course, request.user) } return render_to_response(template_to_render, context) elif request.method == "POST": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) if reg_code_is_valid and not reg_code_already_redeemed: #now redeem the reg code. RegistrationCodeRedemption.create_invoice_generated_registration_redemption(course_registration, request.user) CourseEnrollment.enroll(request.user, course.id) context = { 'redemption_success': True, 'reg_code': registration_code, 'site_name': site_name, 'course': course, } else: context = { 'reg_code_is_valid': reg_code_is_valid, 'reg_code_already_redeemed': reg_code_already_redeemed, 'redemption_success': False, 'reg_code': registration_code, 'site_name': site_name, 'course': course, } return render_to_response(template_to_render, context)
def show_unit_extensions(request, course_id): """ Shows all of the students which have due date extensions for the given unit. """ course = get_course_by_id(SlashSeparatedCourseKey.from_deprecated_string(course_id)) unit = find_unit(course, request.GET.get('url')) return JsonResponse(dump_module_extensions(course, unit))
def test_post_list(self): """ Test the creation of a CCX """ outbox = self.get_outbox() data = { 'master_course_id': self.master_course_key_str, 'max_students_allowed': 111, 'display_name': 'CCX Test Title', 'coach_email': self.coach.email } resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.assertEqual(resp.status_code, status.HTTP_201_CREATED) # check if the response has at least the same data of the request for key, val in data.iteritems(): self.assertEqual(resp.data.get(key), val) # pylint: disable=no-member self.assertIn('ccx_course_id', resp.data) # pylint: disable=no-member # check that the new CCX actually exists course_key = CourseKey.from_string(resp.data.get('ccx_course_id')) # pylint: disable=no-member ccx_course = CustomCourseForEdX.objects.get(pk=course_key.ccx) self.assertEqual( unicode(CCXLocator.from_course_locator(ccx_course.course.id, ccx_course.id)), resp.data.get('ccx_course_id') # pylint: disable=no-member ) # check that the coach user has coach role on the master course coach_role_on_master_course = CourseCcxCoachRole(self.master_course_key) self.assertTrue(coach_role_on_master_course.has_user(self.coach)) # check that the coach has been enrolled in the ccx ccx_course_object = courses.get_course_by_id(course_key) self.assertTrue( CourseEnrollment.objects.filter(course_id=ccx_course_object.id, user=self.coach).exists() ) # check that an email has been sent to the coach self.assertEqual(len(outbox), 1) self.assertIn(self.coach.email, outbox[0].recipients()) # pylint: disable=no-member
def handle(self, *args, **options): print "args = ", args if len(args) > 0: course_id = args[0] else: print self.help return try: course = get_course_by_id(course_id) except Exception as err: if course_id in modulestore().courses: course = modulestore().courses[course_id] else: print "-----------------------------------------------------------------------------" print "Sorry, cannot find course %s" % course_id print "Please provide a course ID or course data directory name, eg content-mit-801rq" return print "-----------------------------------------------------------------------------" print "Computing grades for %s" % (course.id) offline_grade_calculation(course.id)
def instructor_dashboard_2(request, course_id): """ Display the instructor dashboard for a course. """ course = get_course_by_id(course_id, depth=None) access = { "admin": request.user.is_staff, "instructor": has_access(request.user, course, "instructor"), "staff": has_access(request.user, course, "staff"), "forum_admin": has_forum_access(request.user, course_id, FORUM_ROLE_ADMINISTRATOR), } if not access["staff"]: raise Http404() sections = [ _section_course_info(course_id), _section_membership(course_id, access), _section_student_admin(course_id, access), _section_data_download(course_id), _section_analytics(course_id), ] context = { "course": course, "old_dashboard_url": reverse("instructor_dashboard", kwargs={"course_id": course_id}), "sections": sections, } return render_to_response("instructor/instructor_dashboard_2/instructor_dashboard_2.html", context)
def test_patch_detail(self): """ Test for successful patch """ outbox = self.get_outbox() # create a new coach new_coach = AdminFactory.create() data = { 'max_students_allowed': 111, 'display_name': 'CCX Title', 'coach_email': new_coach.email } resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth) self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT) ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id) self.assertEqual(ccx_from_db.max_student_enrollments_allowed, data['max_students_allowed']) self.assertEqual(ccx_from_db.display_name, data['display_name']) self.assertEqual(ccx_from_db.coach.email, data['coach_email']) # check that the coach user has coach role on the master course coach_role_on_master_course = CourseCcxCoachRole(self.master_course_key) self.assertTrue(coach_role_on_master_course.has_user(new_coach)) # check that the coach has been enrolled in the ccx ccx_course_object = courses.get_course_by_id(self.ccx_key) self.assertTrue( CourseEnrollment.objects.filter(course_id=ccx_course_object.id, user=new_coach).exists() ) # check that an email has been sent to the coach self.assertEqual(len(outbox), 1) self.assertIn(new_coach.email, outbox[0].recipients()) # pylint: disable=no-member
def report(self): """Report course grade.""" students = self._get_students() print "\nFetching course data for {0}".format(self.course_id) course = courses.get_course_by_id(self.course_id) request = self._create_request() total = {'users': 0, 'pass': 0, 'notpass': 0} print "Summary Report: Course Name [{0}]".format( course.display_name.encode('utf_8')) for student in students.iterator(): request.user = student total['users'] += 1 certs = GeneratedCertificate.objects.filter( user=student, course_id=self.course_id) for cert in certs.iterator(): grade = grades.grade(cert.user, request, course) summary = grades.progress_summary(student, request, course) self._report_summary(summary) self._add_total(cert.user, grade, total) self._report_total(total)
def _section_course_info(course_id, access): """ Provide data for the corresponding dashboard section """ course = get_course_by_id(course_id, depth=None) course_id_dict = Location.parse_course_id(course_id) section_data = { 'section_key': 'course_info', 'section_display_name': _('Course Info'), 'access': access, 'course_id': course_id, 'course_org': course_id_dict['org'], 'course_num': course_id_dict['course'], 'course_name': course_id_dict['name'], 'course_display_name': course.display_name, 'enrollment_count': CourseEnrollment.num_enrolled_in(course_id), 'has_started': course.has_started(), 'has_ended': course.has_ended(), 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': course_id}), } try: advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo section_data['grade_cutoffs'] = reduce(advance, course.grade_cutoffs.items(), "")[:-2] except Exception: section_data['grade_cutoffs'] = "Not Available" # section_data['offline_grades'] = offline_grades_available(course_id) try: section_data['course_errors'] = [(escape(a), '') for (a, _unused) in modulestore().get_item_errors(course.location)] except Exception: section_data['course_errors'] = [('Error fetching errors', '')] return section_data
def handle(self, *args, **options): if not options['course']: raise CommandError(Command.course_option.help) try: course_key = CourseKey.from_string(options['course']) except InvalidKeyError: course_key = SlashSeparatedCourseKey.from_deprecated_string(options['course']) course = get_course_by_id(course_key) print 'Warning: this command directly edits the list of course tabs in mongo.' print 'Tabs before any changes:' print_course(course) try: if options['delete']: if len(args) != 1: raise CommandError(Command.delete_option.help) num = int(args[0]) if query_yes_no('Deleting tab {0} Confirm?'.format(num), default='no'): primitive_delete(course, num - 1) # -1 for 0-based indexing elif options['insert']: if len(args) != 3: raise CommandError(Command.insert_option.help) num = int(args[0]) tab_type = args[1] name = args[2] if query_yes_no('Inserting tab {0} "{1}" "{2}" Confirm?'.format(num, tab_type, name), default='no'): primitive_insert(course, num - 1, tab_type, name) # -1 as above except ValueError as e: # Cute: translate to CommandError so the CLI error prints nicely. raise CommandError(e)
def is_enabled(cls, request, course_key): """ Returns True if this tool is enabled for the specified course key. """ course = get_course_by_id(course_key) has_updates = CourseUpdatesFragmentView.has_updates(request, course) return UNIFIED_COURSE_TAB_FLAG.is_enabled(course_key) and has_updates
def wrapper(request, course_id): """ Wraps the view function, performing access check, loading the course, and modifying the view's call signature. """ course_key = CourseKey.from_string(course_id) ccx = None if isinstance(course_key, CCXLocator): ccx_id = course_key.ccx ccx = CustomCourseForEdX.objects.get(pk=ccx_id) course_key = ccx.course_id role = CourseCcxCoachRole(course_key) if not role.has_user(request.user): return HttpResponseForbidden( _('You must be a CCX Coach to access this view.')) course = get_course_by_id(course_key, depth=None) # if there is a ccx, we must validate that it is the ccx for this coach if ccx is not None: coach_ccx = get_ccx_for_coach(course, request.user) if coach_ccx is None or coach_ccx.id != ccx.id: return HttpResponseForbidden( _('You must be the coach for this ccx to access this view') ) return view(request, course, ccx)
def verify_for_closed_enrollment(user, cart=None): """ A multi-output helper function. inputs: user: a user object cart: If a cart is provided it uses the same object, otherwise fetches the user's cart. Returns: is_any_course_expired: True if any of the items in the cart has it's enrollment period closed. False otherwise. expired_cart_items: List of courses with enrollment period closed. expired_cart_item_names: List of names of the courses with enrollment period closed. valid_cart_item_tuples: List of courses which are still open for enrollment. """ if cart is None: cart = Order.get_cart_for_user(user) expired_cart_items = [] expired_cart_item_names = [] valid_cart_item_tuples = [] cart_items = cart.orderitem_set.all().select_subclasses() is_any_course_expired = False for cart_item in cart_items: course_key = getattr(cart_item, 'course_id', None) if course_key is not None: course = get_course_by_id(course_key, depth=0) if CourseEnrollment.is_enrollment_closed(user, course): is_any_course_expired = True expired_cart_items.append(cart_item) expired_cart_item_names.append(course.display_name) else: valid_cart_item_tuples.append((cart_item, course)) return is_any_course_expired, expired_cart_items, expired_cart_item_names, valid_cart_item_tuples
def add_cohort(course_key, name, assignment_type): """ Add a cohort to a course. Raises ValueError if a cohort of the same name already exists. """ log.debug("Adding cohort %s to %s", name, course_key) if is_cohort_exists(course_key, name): raise ValueError(_("You cannot create two cohorts with the same name")) try: course = courses.get_course_by_id(course_key) except Http404: raise ValueError("Invalid course_key") cohort = CourseCohort.create( cohort_name=name, course_id=course.id, assignment_type=assignment_type ).course_user_group tracker.emit( "edx.cohort.creation_requested", {"cohort_name": cohort.name, "cohort_id": cohort.id} ) return cohort
def return_fixed_courses(request, courses, action=None): default_length = 8 course_id = request.GET.get("course_id") if course_id: course_id = course_id.replace(".", '/') try: index_course = get_course_by_id(course_id) course_index = (courses.index(index_course) + 1) except: course_index = 0 current_list = courses[course_index:] if len(current_list) > default_length: current_list = current_list[0:default_length] course_list = [] for course in current_list: try: course_json = mobi_course_info(request, course, action) course_list.append(course_json) except: continue return JsonResponse({"count": len(courses), "course-list": course_list})
def post(self, request): """Handle all actions from courses view""" if not request.user.is_staff: raise Http404 action = request.POST.get('action', '') track.views.server_track(request, action, {}, page='courses_sysdashboard') courses = {course.id: course for course in self.get_courses()} if action == 'add_course': gitloc = request.POST.get('repo_location', '').strip().replace(' ', '').replace(';', '') branch = request.POST.get('repo_branch', '').strip().replace(' ', '').replace(';', '') self.msg += self.get_course_from_git(gitloc, branch) elif action == 'del_course': course_id = request.POST.get('course_id', '').strip() course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_found = False if course_key in courses: course_found = True course = courses[course_key] else: try: course = get_course_by_id(course_key) course_found = True except Exception, err: # pylint: disable=broad-except self.msg += _( 'Error - cannot get course with ID {0}<br/><pre>{1}</pre>' ).format( course_key, escape(str(err)) ) is_xml_course = (modulestore().get_modulestore_type(course_key) == XML_MODULESTORE_TYPE) if course_found and is_xml_course: cdir = course.data_dir self.def_ms.courses.pop(cdir) # now move the directory (don't actually delete it) new_dir = "{course_dir}_deleted_{timestamp}".format( course_dir=cdir, timestamp=int(time.time()) ) os.rename(settings.DATA_DIR / cdir, settings.DATA_DIR / new_dir) self.msg += (u"<font color='red'>Deleted " u"{0} = {1} ({2})</font>".format( cdir, course.id, course.display_name)) elif course_found and not is_xml_course: # delete course that is stored with mongodb backend content_store = contentstore() commit = True delete_course(self.def_ms, content_store, course.id, commit) # don't delete user permission groups, though self.msg += \ u"<font color='red'>{0} {1} = {2} ({3})</font>".format( _('Deleted'), course.location.to_deprecated_string(), course.id.to_deprecated_string(), course.display_name)
def look_up_registration_code(request, course_id): """ Look for the registration_code in the database. and check if it is still valid, allowed to redeem or not. """ course_key = CourseKey.from_string(course_id) code = request.GET.get('registration_code') course = get_course_by_id(course_key, depth=0) try: registration_code = CourseRegistrationCode.objects.get(code=code) except CourseRegistrationCode.DoesNotExist: return JsonResponse({ 'is_registration_code_exists': False, 'is_registration_code_valid': False, 'is_registration_code_redeemed': False, 'message': _(u'The enrollment code ({code}) was not found for the {course_name} course.').format( code=code, course_name=course.display_name ) }, status=400) # status code 200: OK by default reg_code_already_redeemed = RegistrationCodeRedemption.is_registration_code_redeemed(code) registration_code_detail_url = reverse('registration_code_details', kwargs={'course_id': unicode(course_id)}) return JsonResponse({ 'is_registration_code_exists': True, 'is_registration_code_valid': registration_code.is_valid, 'is_registration_code_redeemed': reg_code_already_redeemed, 'registration_code_detail_url': registration_code_detail_url }) # status code 200: OK by default
def show_cart(request): """ This view shows cart items. """ cart = Order.get_cart_for_user(request.user) total_cost = cart.total_cost cart_items = cart.orderitem_set.all().select_subclasses() shoppingcart_items = [] for cart_item in cart_items: course_key = getattr(cart_item, 'course_id') if course_key: course = get_course_by_id(course_key, depth=0) shoppingcart_items.append((cart_item, course)) site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME) callback_url = request.build_absolute_uri( reverse("shoppingcart.views.postpay_callback") ) form_html = render_purchase_form_html(cart, callback_url=callback_url) context = { 'order': cart, 'shoppingcart_items': shoppingcart_items, 'amount': total_cost, 'site_name': site_name, 'form_html': form_html, } return render_to_response("shoppingcart/shopping_cart.html", context)
def modify_special_forum_contributors(request, course_id): unique_student_identifier = request.GET.get('unique_student_identifier') rolename = request.GET.get('rolename') action = request.GET.get('action') try: course_id = _check_rights(course_id, request.user, rolename) except UnauthorizedAccessError as e: return HttpResponseBadRequest(e.message) course = get_course_by_id(course_id) _check_custom_roles(course_id) user = get_student_from_identifier(unique_student_identifier) target_is_instructor = has_access(user, 'instructor', course) # cannot revoke instructor if target_is_instructor and action == 'revoke' and rolename == FORUM_ROLE_ADMINISTRATOR: return HttpResponseBadRequest("Cannot revoke instructor forum admin privileges.") try: update_forum_role(course_id, user, rolename, action) except Role.DoesNotExist: return HttpResponseBadRequest("Role does not exist.") response_payload = { 'course_id': course_id.to_deprecated_string(), 'action': action, } return JsonResponse(response_payload)
def _section_course_info(course_id): """ Provide data for the corresponding dashboard section """ course = get_course_by_id(course_id, depth=None) section_data = {} section_data['section_key'] = 'course_info' section_data['section_display_name'] = 'Course Info' section_data['course_id'] = course_id section_data['course_display_name'] = course.display_name section_data['enrollment_count'] = CourseEnrollment.objects.filter(course_id=course_id).count() section_data['has_started'] = course.has_started() section_data['has_ended'] = course.has_ended() try: advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo section_data['grade_cutoffs'] = reduce(advance, course.grade_cutoffs.items(), "")[:-2] except Exception: section_data['grade_cutoffs'] = "Not Available" # section_data['offline_grades'] = offline_grades_available(course_id) try: section_data['course_errors'] = [(escape(a), '') for (a, _) in modulestore().get_item_errors(course.location)] except Exception: section_data['course_errors'] = [('Error fetching errors', '')] return section_data
def instructor_dashboard_2(request, course_id): """ Display the instructor dashboard for a course. """ course = get_course_by_id(course_id, depth=None) access = { 'admin': request.user.is_staff, 'instructor': has_access(request.user, course, 'instructor'), 'staff': has_access(request.user, course, 'staff'), 'forum_admin': has_forum_access( request.user, course_id, FORUM_ROLE_ADMINISTRATOR ), } if not access['staff']: raise Http404() sections = [ _section_course_info(course_id, access), _section_membership(course_id, access), _section_student_admin(course_id, access), _section_data_download(course_id), _section_analytics(course_id), ] context = { 'course': course, 'old_dashboard_url': reverse('instructor_dashboard', kwargs={'course_id': course_id}), 'sections': sections, } return render_to_response('instructor/instructor_dashboard_2/instructor_dashboard_2.html', context)
def is_commentable_cohorted(course_key, commentable_id): """ Args: course_key: CourseKey commentable_id: string Returns: Bool: is this commentable cohorted? Raises: Http404 if the course doesn't exist. """ course = courses.get_course_by_id(course_key) course_cohort_settings = get_course_cohort_settings(course_key) if not course_cohort_settings.is_cohorted or get_team(commentable_id): # this is the easy case :) ans = False elif ( commentable_id in course.top_level_discussion_topic_ids or course_cohort_settings.always_cohort_inline_discussions is False ): # top level discussions have to be manually configured as cohorted # (default is not). # Same thing for inline discussions if the default is explicitly set to False in settings ans = commentable_id in course_cohort_settings.cohorted_discussions else: # inline discussions are cohorted by default ans = True log.debug(u"is_commentable_cohorted(%s, %s) = {%s}", course_key, commentable_id, ans) return ans
def get(self, request, *args, **kwargs): """Shows logs of imports that happened as a result of a git import""" course_id = kwargs.get('course_id') if course_id: course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) # Set mongodb defaults even if it isn't defined in settings mongo_db = { 'host': 'localhost', 'user': '', 'password': '', 'db': 'xlog', } # Allow overrides if hasattr(settings, 'MONGODB_LOG'): for config_item in ['host', 'user', 'password', 'db', ]: mongo_db[config_item] = settings.MONGODB_LOG.get( config_item, mongo_db[config_item]) mongouri = 'mongodb://{user}:{password}@{host}/{db}'.format(**mongo_db) error_msg = '' try: if mongo_db['user'] and mongo_db['password']: mdb = mongoengine.connect(mongo_db['db'], host=mongouri) else: mdb = mongoengine.connect(mongo_db['db'], host=mongo_db['host']) except mongoengine.connection.ConnectionError: log.exception('Unable to connect to mongodb to save log, ' 'please check MONGODB_LOG settings.') if course_id is None: # Require staff if not going to specific course if not request.user.is_staff: raise Http404 cilset = CourseImportLog.objects.all().order_by('-created') else: try: course = get_course_by_id(course_id) except Exception: # pylint: disable=broad-except log.info('Cannot find course {0}'.format(course_id)) raise Http404 # Allow only course team, instructors, and staff if not (request.user.is_staff or CourseInstructorRole(course.id).has_user(request.user) or CourseStaffRole(course.id).has_user(request.user)): raise Http404 log.debug('course_id={0}'.format(course_id)) cilset = CourseImportLog.objects.filter(course_id=course_id).order_by('-created') log.debug('cilset length={0}'.format(len(cilset))) mdb.disconnect() context = {'cilset': cilset, 'course_id': course_id.to_deprecated_string() if course_id else None, 'error_msg': error_msg} return render_to_response(self.template_name, context)
def add_cohort(course_key, name): """ Add a cohort to a course. Raises ValueError if a cohort of the same name already exists. """ log.debug("Adding cohort %s to %s", name, course_key) if CourseUserGroup.objects.filter(course_id=course_key, group_type=CourseUserGroup.COHORT, name=name).exists(): raise ValueError(_("You cannot create two cohorts with the same name")) try: course = courses.get_course_by_id(course_key) except Http404: raise ValueError("Invalid course_key") cohort = CourseUserGroup.objects.create( course_id=course.id, group_type=CourseUserGroup.COHORT, name=name ) tracker.emit( "edx.cohort.creation_requested", {"cohort_name": cohort.name, "cohort_id": cohort.id} ) return cohort
def setUp(self): """ Set up tests """ super(TestFieldOverrides, self).setUp() self.ccx = ccx = CustomCourseForEdX( course_id=self.course.id, display_name='Test CCX', coach=AdminFactory.create()) ccx.save() patch = mock.patch('ccx.overrides.get_current_ccx') self.get_ccx = get_ccx = patch.start() get_ccx.return_value = ccx self.addCleanup(patch.stop) self.addCleanup(RequestCache.clear_request_cache) inject_field_overrides(iter_blocks(ccx.course), self.course, AdminFactory.create()) self.ccx_key = CCXLocator.from_course_locator(self.course.id, ccx.id) self.ccx_course = get_course_by_id(self.ccx_key, depth=None) def cleanup_provider_classes(): """ After everything is done, clean up by un-doing the change to the OverrideFieldData object that is done during the wrap method. """ OverrideFieldData.provider_classes = None self.addCleanup(cleanup_provider_classes)
def publish(self): """Publish pdf of certificate.""" students = self._get_students() print "\nFetching course data for {0}".format(self.course_id) course = courses.get_course_by_id(self.course_id) if not course.has_ended(): raise CertPDFException('This couse is not ended.') print "Fetching enrollment for students({0}).".format(self.course_id) for student in students.iterator(): certs = GeneratedCertificate.objects.filter( user=student, course_id=self.course_id, status=CertificateStatuses.generating) for cert in certs.iterator(): if cert.download_url: if not self.noop: cert.status = CertificateStatuses.downloadable print "Publish {0}'s certificate : Status {1}".format( student.username, cert.status) cert.save() else: print "Publish {0}'s certificate : Status {1} (Noop)".format( student.username, cert.status) else: print "Publish {0}'s certificate : Error download_url is empty.".format( student.username)
def show_unit_extensions(request, course_id): """ Shows all of the students which have due date extensions for the given unit. """ course = get_course_by_id(course_id) unit = find_unit(course, request.GET.get('url')) return JsonResponse(dump_module_extensions(course, unit))
def is_commentable_cohorted(course_key, commentable_id): """ Args: course_key: CourseKey commentable_id: string Returns: Bool: is this commentable cohorted? Raises: Http404 if the course doesn't exist. """ course = courses.get_course_by_id(course_key) if not course.is_cohorted: # this is the easy case :) ans = False elif commentable_id in course.top_level_discussion_topic_ids: # top level discussions have to be manually configured as cohorted # (default is not) ans = commentable_id in course.cohorted_discussions else: # inline discussions are cohorted by default ans = True log.debug(u"is_commentable_cohorted({0}, {1}) = {2}".format( course_key, commentable_id, ans )) return ans
def iterate_grades_for(course_or_id, students): """ Given a course_id and an iterable of students (User), yield a GradeResult for every student enrolled in the course. GradeResult is a named tuple of: (student, gradeset, err_msg) If an error occurred, gradeset will be an empty dict and err_msg will be an exception message. If there was no error, err_msg is an empty string. The gradeset is a dictionary with the following fields: - grade : A final letter grade. - percent : The final percent for the class (rounded up). - section_breakdown : A breakdown of each section that makes up the grade. (For display) - grade_breakdown : A breakdown of the major components that make up the final grade. (For display) - raw_scores: contains scores for every graded module """ if isinstance(course_or_id, (basestring, CourseKey)): course = get_course_by_id(course_or_id) else: course = course_or_id for student in students: with dog_stats_api.timer('lms.grades.iterate_grades_for', tags=[u'action:{}'.format(course.id)]): try: gradeset = summary(student, course) yield GradeResult(student, gradeset, "") except Exception as exc: # pylint: disable=broad-except # Keep marching on even if this student couldn't be graded for # some reason, but log it for future reference. log.exception( 'Cannot grade student %s (%s) in course %s because of exception: %s', student.username, student.id, course.id, exc.message ) yield GradeResult(student, {}, exc.message)
def handle(self, *args, **__options): """ Migrates existing SGA submissions. """ if not args: raise CommandError('Please specify the course id.') if len(args) > 1: raise CommandError('Too many arguments.') course_id = args[0] course_key = CourseKey.from_string(course_id) course = get_course_by_id(course_key) student_modules = StudentModule.objects.filter( course_id=course.id).filter(module_state_key__contains='ev_sa') blocks = {} for student_module in student_modules: block_id = student_module.module_state_key if block_id.block_type != 'ev_sa': continue block = blocks.get(block_id) if not block: blocks[block_id] = block = modulestore().get_item(block_id) state = json.loads(student_module.state) sha1 = state.get('uploaded_sha1') if not sha1: continue student = student_module.student submission_id = block.student_submission_id( anonymous_id_for_user(student, course.id)) answer = { "sha1": sha1, "filename": state.get('uploaded_filename'), "mimetype": state.get('uploaded_mimetype'), } submission = submissions_api.create_submission( submission_id, answer) score = state.get('score') # float if score: submissions_api.set_score(submission['uuid'], int(score), block.max_score())
def look_up_registration_code(request, course_id): """ Look for the registration_code in the database. and check if it is still valid, allowed to redeem or not. """ course_key = CourseKey.from_string(course_id) code = request.GET.get('registration_code') course = get_course_by_id(course_key, depth=0) try: registration_code = CourseRegistrationCode.objects.get(code=code) except CourseRegistrationCode.DoesNotExist: return JsonResponse( { 'is_registration_code_exists': False, 'is_registration_code_valid': False, 'is_registration_code_redeemed': False, 'message': _('The enrollment code ({code}) was not found for the {course_name} course.' ).format(code=code, course_name=course.display_name) }, status=400) # status code 200: OK by default reg_code_already_redeemed = RegistrationCodeRedemption.is_registration_code_redeemed( code) registration_code_detail_url = reverse( 'registration_code_details', kwargs={'course_id': unicode(course_id)}) return JsonResponse({ 'is_registration_code_exists': True, 'is_registration_code_valid': registration_code.is_valid, 'is_registration_code_redeemed': reg_code_already_redeemed, 'registration_code_detail_url': registration_code_detail_url }) # status code 200: OK by default
def wrapper(request, course_id): """ Wraps the view function, performing access check, loading the course, and modifying the view's call signature. """ course_key = CourseKey.from_string(course_id) ccx = None if isinstance(course_key, CCXLocator): ccx_id = course_key.ccx try: ccx = CustomCourseForEdX.objects.get(pk=ccx_id) except CustomCourseForEdX.DoesNotExist: raise Http404 if ccx: course_key = ccx.course_id course = get_course_by_id(course_key, depth=None) if not course.enable_ccx: raise Http404 else: is_staff = has_access(request.user, 'staff', course) is_instructor = has_access(request.user, 'instructor', course) if is_staff or is_instructor: # if user is staff or instructor then he can view ccx coach dashboard. return view(request, course, ccx) else: # if there is a ccx, we must validate that it is the ccx for this coach role = CourseCcxCoachRole(course_key) if not role.has_user(request.user): return HttpResponseForbidden( _('You must be a CCX Coach to access this view.')) elif ccx is not None: coach_ccx = get_ccx_by_ccx_id(course, request.user, ccx.id) if coach_ccx is None: return HttpResponseForbidden( _('You must be the coach for this ccx to access this view' )) return view(request, course, ccx)
def _listen_for_passing_grade(sender, user, course_id, **kwargs): # pylint: disable=unused-argument """ Listen for a learner passing a course, send cert generation task, downstream signal from COURSE_GRADE_CHANGED """ # No flags enabled if (not waffle.waffle().is_enabled(waffle.SELF_PACED_ONLY) and not waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY)): return if courses.get_course_by_id(course_id, depth=0).self_paced: if not waffle.waffle().is_enabled(waffle.SELF_PACED_ONLY): return else: if not waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY): return if fire_ungenerated_certificate_task(user, course_id): log.info( u'Certificate generation task initiated for {user} : {course} via passing grade' .format(user=user.id, course=course_id))
def ensure_certif(request, course_id): user_id = request.user.id username = request.user.username course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_tma = get_course_by_id(course_key) is_graded = course_tma.is_graded grade_cutoffs = modulestore().get_course( course_key, depth=0).grade_cutoffs['Pass'] * 100 grading_note = CourseGradeFactory().create(request.user, course_tma) passed = grading_note.passed percent = float(int(grading_note.percent * 1000) / 10) overall_progress = get_overall_progress(request.user.id, course_key) context = { 'passed': passed, 'percent': percent, 'is_graded': is_graded, 'grade_cutoffs': grade_cutoffs, 'overall_progress': overall_progress } return JsonResponse(context)
def delete_redemption_entry(request, code_redemption, course_key): """ delete the redemption entry from the table and unenroll the user who used the registration code for the enrollment and send him/her the unenrollment email. """ user = code_redemption.redeemed_by email_address = code_redemption.redeemed_by.email full_name = code_redemption.redeemed_by.profile.name CourseEnrollment.unenroll(user, course_key, skip_refund=True) course = get_course_by_id(course_key, depth=0) email_params = get_email_params(course, True, secure=request.is_secure()) email_params['message_type'] = 'enrolled_unenroll' email_params['email_address'] = email_address email_params['full_name'] = full_name send_mail_to_student(email_address, email_params) # remove the redemption entry from the database. log.info('deleting redemption entry (%s) from the database.', code_redemption.id) code_redemption.delete()
def test_get_static_tab_fragment(self): self.setup_user() course = get_course_by_id(self.course.id) self.addCleanup(set_current_request, None) request = get_mock_request(self.user) tab = xmodule_tabs.CourseTabList.get_tab_by_slug( course.tabs, 'new_tab') # Test render works okay tab_content = get_static_tab_fragment(request, course, tab).content self.assertIn(text_type(self.course.id), tab_content) self.assertIn('static_tab', tab_content) # Test when render raises an exception with patch('courseware.views.views.get_module') as mock_module_render: mock_module_render.return_value = MagicMock(render=Mock( side_effect=Exception('Render failed!'))) static_tab_content = get_static_tab_fragment(request, course, tab).content self.assertIn("this module is temporarily unavailable", static_tab_content)
def rows(self): for course_id in course_ids_between(self.start_word, self.end_word): cur_course = get_course_by_id(course_id) university = cur_course.org course = cur_course.number + " " + cur_course.display_name_with_default total_payments_collected = CertificateItem.verified_certificates_monetary_field_sum( course_id, 'purchased', 'unit_cost') service_fees = CertificateItem.verified_certificates_monetary_field_sum( course_id, 'purchased', 'service_fee') num_refunds = CertificateItem.verified_certificates_count( course_id, "refunded") amount_refunds = CertificateItem.verified_certificates_monetary_field_sum( course_id, 'refunded', 'unit_cost') num_transactions = ( num_refunds * 2) + CertificateItem.verified_certificates_count( course_id, "purchased") yield [ university, course, num_transactions, total_payments_collected, service_fees, num_refunds, amount_refunds ]
def _get_context(self): """ """ if self.is_course_staff(): return dict( user_is_staff=True, rubric=self.rubric, users=self.get_student_list(), reload_url=self.runtime.local_resource_url(self, 'public/img/reload-icon.png'), cohorts=get_cohort_names(get_course_by_id(self.course_id)), teams=self.api_teams.get_course_teams(unicode(self.course_id)), grading_message=self.grading_message, ) comment = self.get_comment() return dict( user_is_staff=False, grading_message=comment if comment and self.score else self.grading_message, score=self.score, max_score=self.points )
def _listen_for_certificate_whitelist_append(sender, instance, **kwargs): # pylint: disable=unused-argument switches = waffle.waffle() # No flags enabled if ( not switches.is_enabled(waffle.SELF_PACED_ONLY) and not switches.is_enabled(waffle.INSTRUCTOR_PACED_ONLY) ): return if courses.get_course_by_id(instance.course_id, depth=0).self_paced: if not switches.is_enabled(waffle.SELF_PACED_ONLY): return else: if not switches.is_enabled(waffle.INSTRUCTOR_PACED_ONLY): return fire_ungenerated_certificate_task(instance.user, instance.course_id) log.info(u'Certificate generation task initiated for {user} : {course} via whitelist'.format( user=instance.user.id, course=instance.course_id ))
def remove_master_course_staff_from_ccx_for_existing_ccx(apps, schema_editor): """ Remove all staff and instructors of master course from respective CCX(s). Arguments: apps (Applications): Apps in edX platform. schema_editor (SchemaEditor): For editing database schema i.e create, delete field (column) """ CustomCourseForEdX = apps.get_model("ccx", "CustomCourseForEdX") list_ccx = CustomCourseForEdX.objects.all() for ccx in list_ccx: if ccx.course_id.deprecated: # prevent migration for deprecated course ids. continue ccx_locator = CCXLocator.from_course_locator(ccx.course_id, unicode(ccx.id)) remove_master_course_staff_from_ccx(get_course_by_id(ccx.course_id), ccx_locator, ccx.display_name, send_email=False)
def add_cohort(course_key, name): """ Add a cohort to a course. Raises ValueError if a cohort of the same name already exists. """ log.debug("Adding cohort %s to %s", name, course_key) if CourseUserGroup.objects.filter(course_id=course_key, group_type=CourseUserGroup.COHORT, name=name).exists(): raise ValueError("Can't create two cohorts with the same name") try: course = courses.get_course_by_id(course_key) except Http404: raise ValueError("Invalid course_key") return CourseUserGroup.objects.create( course_id=course.id, group_type=CourseUserGroup.COHORT, name=name )
def move_to_verified_cohort(sender, instance, **kwargs): # pylint: disable=unused-argument """ If the learner has changed modes, update assigned cohort iff the course is using the Automatic Verified Track Cohorting MVP feature. """ course_key = instance.course_id verified_cohort_enabled = VerifiedTrackCohortedCourse.is_verified_track_cohort_enabled( course_key) verified_cohort_name = VerifiedTrackCohortedCourse.verified_cohort_name_for_course( course_key) if verified_cohort_enabled and (instance.mode != instance._old_mode): # pylint: disable=protected-access if not is_course_cohorted(course_key): log.error( "Automatic verified cohorting enabled for course '%s', but course is not cohorted", course_key) else: existing_cohorts = get_course_cohorts(get_course_by_id(course_key), CourseCohort.MANUAL) if any(cohort.name == verified_cohort_name for cohort in existing_cohorts): args = { 'course_id': unicode(course_key), 'user_id': instance.user.id, 'verified_cohort_name': verified_cohort_name } # Do the update with a 3-second delay in hopes that the CourseEnrollment transaction has been # completed before the celery task runs. We want a reasonably short delay in case the learner # immediately goes to the courseware. sync_cohort_with_mode.apply_async(kwargs=args, countdown=3) # In case the transaction actually was not committed before the celery task runs, # run it again after 5 minutes. If the first completed successfully, this task will be a no-op. sync_cohort_with_mode.apply_async(kwargs=args, countdown=300) else: log.error( "Automatic verified cohorting enabled for course '%s', but cohort named '%s' does not exist.", course_key, verified_cohort_name, )
def handle(self, *args, **options): if not options['course']: raise CommandError(Command.course_option.help) try: course_key = CourseKey.from_string(options['course']) except InvalidKeyError: course_key = SlashSeparatedCourseKey.from_deprecated_string( options['course']) course = get_course_by_id(course_key) print 'Warning: this command directly edits the list of course tabs in mongo.' print 'Tabs before any changes:' print_course(course) try: if options['delete']: if len(args) != 1: raise CommandError(Command.delete_option.help) num = int(args[0]) if query_yes_no('Deleting tab {0} Confirm?'.format(num), default='no'): tabs.primitive_delete(course, num - 1) # -1 for 0-based indexing elif options['insert']: if len(args) != 3: raise CommandError(Command.insert_option.help) num = int(args[0]) tab_type = args[1] name = args[2] if query_yes_no( 'Inserting tab {0} "{1}" "{2}" Confirm?'.format( num, tab_type, name), default='no'): tabs.primitive_insert(course, num - 1, tab_type, name) # -1 as above except ValueError as e: # Cute: translate to CommandError so the CLI error prints nicely. raise CommandError(e)
def is_commentable_divided(course_key, commentable_id, course_discussion_settings=None): """ Args: course_key: CourseKey commentable_id: string course_discussion_settings: CourseDiscussionSettings model instance (optional). If not supplied, it will be retrieved via the course_key. Returns: Bool: is this commentable divided, meaning that learners are divided into groups (either Cohorts or Enrollment Tracks) and only see posts within their group? Raises: Http404 if the course doesn't exist. """ if not course_discussion_settings: course_discussion_settings = get_course_discussion_settings(course_key) course = courses.get_course_by_id(course_key) if not course_discussion_division_enabled( course_discussion_settings) or get_team(commentable_id): # this is the easy case :) ans = False elif (commentable_id in course.top_level_discussion_topic_ids or course_discussion_settings.always_divide_inline_discussions is False): # top level discussions have to be manually configured as divided # (default is not). # Same thing for inline discussions if the default is explicitly set to False in settings ans = commentable_id in course_discussion_settings.divided_discussions else: # inline discussions are divided by default ans = True log.debug(u"is_commentable_divided(%s, %s) = {%s}", course_key, commentable_id, ans) return ans
def course_grade_from_course_id(learner, course_id): """Get the edx-platform's course grade for this enrollment IMPORTANT: Do not use in API calls as this is an expensive operation. Only use in async or pipeline. We handle the exception so that we return a specific `CourseNotFound` instead of the non-specific `Http404` edx-platform `get_course_by_id` function raises a generic `Http404` if it cannot find a course in modulestore. We trap this and raise our own `CourseNotFound` exception as it is more specific. TODO: Consider optional kwarg param or Figures setting to log performance. Bonus points: Make id a decorator """ try: course = get_course_by_id(course_key=as_course_key(course_id)) except Http404: raise CourseNotFound('{}'.format(str(course_id))) course._field_data_cache = {} # pylint: disable=protected-access course.set_grading_policy(course.grading_policy) return course_grade(learner, course)
def instructor_dashboard_2(request, course_id): """ Display the instructor dashboard for a course. """ course = get_course_by_id(course_id, depth=None) access = { 'admin': request.user.is_staff, 'instructor': has_access(request.user, course, 'instructor'), 'staff': has_access(request.user, course, 'staff'), 'forum_admin': has_forum_access(request.user, course_id, FORUM_ROLE_ADMINISTRATOR), } if not access['staff']: raise Http404() sections = [ _section_course_info(course_id), _section_membership(course_id, access), _section_student_admin(course_id, access), _section_data_download(course_id), _section_analytics(course_id), ] context = { 'course': course, 'old_dashboard_url': reverse('instructor_dashboard', kwargs={'course_id': course_id}), 'sections': sections, } return render_to_response( 'instructor/instructor_dashboard_2/instructor_dashboard_2.html', context)
def send_grandfather_email(self, user, certificates, mock_run=False): """ Send the 'grandfathered' email informing historical students that they may now post their certificates on their LinkedIn profiles. """ courses_list = [] for cert in certificates: course = get_course_by_id(cert.course_id) course_url = 'https://{}{}'.format( settings.SITE_NAME, reverse('course_root', kwargs={'course_id': cert.course_id}) ) course_title = course.display_name_with_default course_img_url = 'https://{}{}'.format(settings.SITE_NAME, course_image_url(course)) course_end_date = course.end.strftime('%b %Y') course_org = course.org courses_list.append({ 'course_url': course_url, 'course_org': course_org, 'course_title': course_title, 'course_image_url': course_img_url, 'course_end_date': course_end_date, 'linkedin_add_url': self.certificate_url(cert), }) context = { 'courses_list': courses_list, 'num_courses': len(courses_list), 'google_analytics': settings.GOOGLE_ANALYTICS_LINKEDIN, } body = render_to_string('linkedin/linkedin_email.html', context) subject = u'{}, Add your Achievements to your LinkedIn Profile'.format(user.profile.name) if mock_run: return True else: return self.send_email(user, subject, body)
def offline_grade_calculation(course_id): ''' Compute grades for all students for a specified course, and save results to the DB. ''' tstart = time.time() enrolled_students = User.objects.filter(courseenrollment__course_id=course_id).prefetch_related("groups").order_by('username') enc = MyEncoder() class DummyRequest(object): META = {} def __init__(self): return def get_host(self): return 'edx.mit.edu' def is_secure(self): return False request = DummyRequest() print "%d enrolled students" % len(enrolled_students) course = get_course_by_id(course_id) for student in enrolled_students: gradeset = grades.grade(student, request, course, keep_raw_scores=True) gs = enc.encode(gradeset) 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 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 setUp(self): """ Set up tests """ super(TestCCXGrades, self).setUp() # Create instructor account self.coach = coach = AdminFactory.create() self.client.login(username=coach.username, password="******") # Create CCX role = CourseCcxCoachRole(self._course.id) role.add_users(coach) ccx = CcxFactory(course_id=self._course.id, coach=self.coach) # override course grading policy and make last section invisible to students override_field_for_ccx( ccx, self._course, 'grading_policy', { 'GRADER': [{ 'drop_count': 0, 'min_count': 2, 'short_label': 'HW', 'type': 'Homework', 'weight': 1 }], 'GRADE_CUTOFFS': { 'Pass': 0.75 }, }) override_field_for_ccx(ccx, self.sections[-1], 'visible_to_staff_only', True) # create a ccx locator and retrieve the course structure using that key # which emulates how a student would get access. self.ccx_key = CCXLocator.from_course_locator(self._course.id, ccx.id) self.course = get_course_by_id(self.ccx_key, depth=None) setup_students_and_grades(self) self.client.login(username=coach.username, password="******") self.addCleanup(RequestCache.clear_request_cache)
def grade_collector_stat(): """ Task for update user grades. """ this_update_date = datetime.now() users_by_course = get_items_for_grade_update() collected_stat = [] for course_string_id, users in users_by_course.iteritems(): try: course_key = CourseKey.from_string(course_string_id) course = get_course_by_id(course_key, depth=0) except (InvalidKeyError, Http404): continue with modulestore().bulk_operations(course_key): for user in users: grades = get_grade_summary(user, course) if not grades: continue exam_info = OrderedDict() for grade in grades['section_breakdown']: exam_info[grade['label']] = int(grade['percent'] * 100.0) exam_info['total'] = int(grades['percent'] * 100.0) collected_stat.append(({ 'course_id': course_key, 'student_id': user }, { 'exam_info': json.dumps(exam_info), 'total': grades['percent'] })) with transaction.atomic(): for key_values, additional_info in collected_stat: key_values['defaults'] = additional_info GradeStatistic.objects.update_or_create(**key_values) LastGradeStatUpdate(last_update=this_update_date).save()
def offline_grade_calculation(course_key): ''' Compute grades for all students for a specified course, and save results to the DB. ''' tstart = time.time() enrolled_students = User.objects.filter( courseenrollment__course_id=course_key, courseenrollment__is_active=1).prefetch_related("groups").order_by( 'username') enc = MyEncoder() print "{} enrolled students".format(len(enrolled_students)) course = get_course_by_id(course_key) for student in enrolled_students: request = DummyRequest() request.user = student request.session = {} gradeset = grades.grade(student, request, course, keep_raw_scores=True) gs = enc.encode(gradeset) ocg, _created = models.OfflineComputedGrade.objects.get_or_create( user=student, course_id=course_key) ocg.gradeset = gs ocg.save() 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_key, seconds=dt, nstudents=len(enrolled_students)) ocgl.save() print ocgl print "All Done!"
def add_cohort(course_key, name, assignment_type): """ Add a cohort to a course. Raises ValueError if a cohort of the same name already exists. """ log.debug("Adding cohort %s to %s", name, course_key) if is_cohort_exists(course_key, name): raise ValueError(_("You cannot create two cohorts with the same name")) try: course = courses.get_course_by_id(course_key) except Http404: raise ValueError("Invalid course_key") cohort = CourseCohort.create( cohort_name=name, course_id=course.id, assignment_type=assignment_type).course_user_group tracker.emit("edx.cohort.creation_requested", { "cohort_name": cohort.name, "cohort_id": cohort.id }) return cohort
def base_process(self, request, course_id): """ Preprocess request, check permission and select course. """ course_id = request.POST.get('course_id', course_id) try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: log.error( "Unable to find course with course key %s while loading the Instructor Analytics Dashboard.", course_id) return HttpResponseBadRequest() course = get_course_by_id(course_key, depth=0) if not has_access(request.user, self.group_name, course): log.error("Statistics not available for user type `%s`", request.user) return HttpResponseForbidden() return self.process(request, course_key=course_key, course=course, course_id=course_id)
def get_group_names_by_id(course_discussion_settings): """ Creates of a dict of group_id to learner-facing group names, for the division_scheme in use as specified by course_discussion_settings. Args: course_discussion_settings: CourseDiscussionSettings model instance Returns: dict of group_id to learner-facing group names. If no division_scheme is in use, returns an empty dict. """ division_scheme = _get_course_division_scheme(course_discussion_settings) course_key = course_discussion_settings.course_id if division_scheme == CourseDiscussionSettings.COHORT: return get_cohort_names(courses.get_course_by_id(course_key)) elif division_scheme == CourseDiscussionSettings.ENROLLMENT_TRACK: # We negate the group_ids from dynamic partitions so that they will not conflict # with cohort IDs (which are an auto-incrementing integer field, starting at 1). return { -1 * group.id: group.name for group in _get_enrollment_track_groups(course_key) } else: return {}
def certificate_url(self, certificate): """ Generates a certificate URL based on LinkedIn's documentation. The documentation is from a Word document: DAT_DOCUMENTATION_v3.12.docx """ course = get_course_by_id(certificate.course_id) tracking_code = '-'.join([ 'eml', 'prof', # the 'product'--no idea what that's supposed to mean 'edX', # Partner's name course.number, # Certificate's name 'gf']) query = [ ('pfCertificationName', course.display_name_with_default), ('pfAuthorityId', settings.LINKEDIN_API['COMPANY_ID']), ('pfCertificationUrl', certificate.download_url), ('pfLicenseNo', certificate.course_id), ('pfCertStartDate', course.start.strftime('%Y%m')), ('_mSplash', '1'), ('trk', tracking_code), ('startTask', 'CERTIFICATION_NAME'), ('force', 'true')] return 'http://www.linkedin.com/profile/guided?' + urllib.urlencode(query)
def handle(self, *args, **options): course_id = args[0] try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: course_key = SlashSeparatedCourseKey.from_deprecated_string( course_id) try: course = get_course_by_id(course_key) except Exception as err: # pylint: disable=broad-except print "Course not found." return enrolled_students = User.objects.filter( courseenrollment__course_id=course_key, courseenrollment__is_active=1, ).values_list('username', flat=True) for student in enrolled_students: print student
def _get_course_cohort_settings(course_key): """ Return cohort settings for a course. NOTE that the only non-deprecated fields in CourseCohortSettings are `course_id` and `is_cohorted`. Other fields should only be used for migration purposes. Arguments: course_key: CourseKey Returns: A CourseCohortSettings object. NOTE that the only non-deprecated field in CourseCohortSettings are `course_id` and `is_cohorted`. Other fields should only be used for migration purposes. Raises: Http404 if course_key is invalid. """ try: course_cohort_settings = CourseCohortsSettings.objects.get(course_id=course_key) except CourseCohortsSettings.DoesNotExist: course = courses.get_course_by_id(course_key) course_cohort_settings = migrate_cohort_settings(course) return course_cohort_settings
def validate_course(self): """ Validates that enrollment is available on course """ try: course_key = CourseKey.from_string(self.params['course_id']) course = get_course_by_id(course_key) if not course.self_paced: if not enrollment_has_started(course): # Translators: This is for the ForUs API self.errors['course_id'].append( _('The course has not yet been opened for enrollment, ' 'please go back to the ForUs portal and enroll in other courses' )) if enrollment_has_ended(course): # Translators: This is for the ForUs API self.errors['course_id'].append( _('Enrollment for this course has been closed, ' 'please go back to the ForUs portal and enroll in other courses' )) except InvalidKeyError: log.warning( u"User {username} tried to {action} with invalid course id: {course_id}" .format( username=self.params.get('username'), action=self.params.get('enrollment_action'), course_id=self.params.get('course_id'), )) self.mark_as_invalid('course_id', _('course id')) except Http404: # Translators: This is for the ForUs API self.errors['course_id'].append( _('The requested course does not exist'))