def clean_course_id(model_form, is_required=True): """ Cleans and validates a course_id for use with a Django ModelForm. Arguments: model_form (form.ModelForm): The form that has a course_id. is_required (Boolean): Default True. When True, validates that the course_id is not empty. In all cases, when course_id is supplied, validates that it is a valid course. Returns: (CourseKey) The cleaned and validated course_id as a CourseKey. NOTE: Use this method in model forms instead of a custom "clean_course_id" method! """ cleaned_id = model_form.cleaned_data["course_id"] if not cleaned_id and not is_required: return None try: course_key = CourseKey.from_string(cleaned_id) except InvalidKeyError: msg = u'Course id invalid. Entered course id was: "{0}".'.format(cleaned_id) raise forms.ValidationError(msg) if not modulestore().has_course(course_key): msg = u'Course not found. Entered course id was: "{0}".'.format(text_type(course_key)) raise forms.ValidationError(msg) return course_key
def _get_thread_and_context(request, thread_id, retrieve_kwargs=None): """ Retrieve the given thread and build a serializer context for it, returning both. This function also enforces access control for the thread (checking both the user's access to the course and to the thread's cohort if applicable). Raises Http404 if the thread does not exist or the user cannot access it. """ retrieve_kwargs = retrieve_kwargs or {} try: if "mark_as_read" not in retrieve_kwargs: retrieve_kwargs["mark_as_read"] = False cc_thread = Thread(id=thread_id).retrieve(**retrieve_kwargs) course_key = CourseKey.from_string(cc_thread["course_id"]) course = _get_course_or_404(course_key, request.user) context = get_context(course, request, cc_thread) if ( not context["is_requester_privileged"] and cc_thread["group_id"] and is_commentable_cohorted(course.id, cc_thread["commentable_id"]) ): requester_cohort = get_cohort_id(request.user, course.id) if requester_cohort is not None and cc_thread["group_id"] != requester_cohort: raise Http404 return cc_thread, context except CommentClientRequestError: # params are validated at a higher level, so the only possible request # error is if the thread doesn't exist raise Http404
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 add_coupon(request, course_id): """ add coupon in the Coupons Table """ code = request.POST.get('code') # check if the code is already in the Coupons Table and active try: course_id = CourseKey.from_string(course_id) coupon = Coupon.objects.get(is_active=True, code=code, course_id=course_id) except Coupon.DoesNotExist: # check if the coupon code is in the CourseRegistrationCode Table course_registration_code = CourseRegistrationCode.objects.filter(code=code) if course_registration_code: return JsonResponse( {'message': _("The code ({code}) that you have tried to define is already in use as a registration code").format(code=code)}, status=400) # status code 400: Bad Request description = request.POST.get('description') course_id = request.POST.get('course_id') try: discount = int(request.POST.get('discount')) except ValueError: return JsonResponse({ 'message': _("Please Enter the Integer Value for Coupon Discount") }, status=400) # status code 400: Bad Request if discount > 100 or discount < 0: return JsonResponse({ 'message': _("Please Enter the Coupon Discount Value Less than or Equal to 100") }, status=400) # status code 400: Bad Request expiration_date = None if request.POST.get('expiration_date'): expiration_date = request.POST.get('expiration_date') try: expiration_date = datetime.datetime.strptime(expiration_date, "%m/%d/%Y").replace(tzinfo=pytz.UTC) + datetime.timedelta(days=1) except ValueError: return JsonResponse({ 'message': _("Please enter the date in this format i-e month/day/year") }, status=400) # status code 400: Bad Request coupon = Coupon( code=code, description=description, course_id=course_id, percentage_discount=discount, created_by_id=request.user.id, expiration_date=expiration_date ) coupon.save() return JsonResponse( {'message': _("coupon with the coupon code ({code}) added successfully").format(code=code)} ) if coupon: return JsonResponse( {'message': _("coupon with the coupon code ({code}) already exists for this course").format(code=code)}, status=400) # status code 400: Bad Request
def create_thread(request, thread_data): """ Create a thread. Arguments: request: The django request object used for build_absolute_uri and determining the requesting user. thread_data: The data for the created thread. Returns: The created thread; see discussion.rest_api.views.ThreadViewSet for more detail. """ course_id = thread_data.get("course_id") user = request.user if not course_id: raise ValidationError({"course_id": ["This field is required."]}) try: course_key = CourseKey.from_string(course_id) course = _get_course(course_key, user) except InvalidKeyError: raise ValidationError({"course_id": ["Invalid value."]}) # lint-amnesty, pylint: disable=raise-missing-from if not discussion_open_for_user(course, user): raise DiscussionBlackOutException context = get_context(course, request) _check_initializable_thread_fields(thread_data, context) discussion_settings = CourseDiscussionSettings.get(course_key) if ("group_id" not in thread_data and is_commentable_divided( course_key, thread_data.get("topic_id"), discussion_settings)): thread_data = thread_data.copy() thread_data["group_id"] = get_group_id_for_user( user, discussion_settings) serializer = ThreadSerializer(data=thread_data, context=context) actions_form = ThreadActionsForm(thread_data) if not (serializer.is_valid() and actions_form.is_valid()): raise ValidationError( dict( list(serializer.errors.items()) + list(actions_form.errors.items()))) serializer.save() cc_thread = serializer.instance thread_created.send(sender=None, user=user, post=cc_thread) api_thread = serializer.data _do_extra_actions(api_thread, cc_thread, list(thread_data.keys()), actions_form, context, request) track_thread_created_event(request, course, cc_thread, actions_form.cleaned_data["following"]) return api_thread
def create_thread(request, thread_data): """ Create a thread. Arguments: request: The django request object used for build_absolute_uri and determining the requesting user. thread_data: The data for the created thread. Returns: The created thread; see discussion_api.views.ThreadViewSet for more detail. """ course_id = thread_data.get("course_id") user = request.user if not course_id: raise ValidationError({"course_id": ["This field is required."]}) try: course_key = CourseKey.from_string(course_id) course = _get_course_or_404(course_key, user) except (Http404, InvalidKeyError): raise ValidationError({"course_id": ["Invalid value."]}) context = get_context(course, request) _check_initializable_thread_fields(thread_data, context) if ( "group_id" not in thread_data and is_commentable_cohorted(course_key, thread_data.get("topic_id")) ): thread_data = thread_data.copy() thread_data["group_id"] = get_cohort_id(user, course_key) serializer = ThreadSerializer(data=thread_data, context=context) actions_form = ThreadActionsForm(thread_data) if not (serializer.is_valid() and actions_form.is_valid()): raise ValidationError(dict(serializer.errors.items() + actions_form.errors.items())) serializer.save() cc_thread = serializer.object thread_created.send(sender=None, user=user, post=cc_thread) api_thread = serializer.data _do_extra_actions(api_thread, cc_thread, thread_data.keys(), actions_form, context) track_forum_event( request, THREAD_CREATED_EVENT_NAME, course, cc_thread, get_thread_created_event_data(cc_thread, followed=actions_form.cleaned_data["following"]) ) return api_thread
def _create_library(self, org="org", library="lib", display_name="Test Library"): """ Helper method used to create a library. Uses the REST API. """ response = self.client.ajax_post( LIBRARY_REST_URL, {"org": org, "library": library, "display_name": display_name} ) self.assertEqual(response.status_code, 200) lib_info = parse_json(response) lib_key = CourseKey.from_string(lib_info["library_key"]) self.assertIsInstance(lib_key, LibraryLocator) return lib_key
def _create_library(self, org="org", library="lib", display_name="Test Library"): """ Helper method used to create a library. Uses the REST API. """ response = self.client.ajax_post(LIBRARY_REST_URL, { 'org': org, 'library': library, 'display_name': display_name, }) self.assertEqual(response.status_code, 200) lib_info = parse_json(response) lib_key = CourseKey.from_string(lib_info['library_key']) self.assertIsInstance(lib_key, LibraryLocator) return lib_key
def get_filename_safe_course_id(course_id, replacement_char='_'): """ Create a representation of a course_id that can be used safely in a filepath. """ try: course_key = CourseKey.from_string(course_id) filename = unicode(replacement_char).join([course_key.org, course_key.course, course_key.run]) except InvalidKeyError: # If the course_id doesn't parse, we will still return a value here. filename = course_id # The safest characters are A-Z, a-z, 0-9, <underscore>, <period> and <hyphen>. # We represent the first four with \w. # TODO: Once we support courses with unicode characters, we will need to revisit this. return re.sub(r'[^\w\.\-]', unicode(replacement_char), filename)
def create_thread(request, thread_data): """ Create a thread. Arguments: request: The django request object used for build_absolute_uri and determining the requesting user. thread_data: The data for the created thread. Returns: The created thread; see discussion.rest_api.views.ThreadViewSet for more detail. """ course_id = thread_data.get("course_id") user = request.user if not course_id: raise ValidationError({"course_id": ["This field is required."]}) try: course_key = CourseKey.from_string(course_id) course = _get_course(course_key, user) except InvalidKeyError: raise ValidationError({"course_id": ["Invalid value."]}) context = get_context(course, request) _check_initializable_thread_fields(thread_data, context) discussion_settings = get_course_discussion_settings(course_key) if ( "group_id" not in thread_data and is_commentable_divided(course_key, thread_data.get("topic_id"), discussion_settings) ): thread_data = thread_data.copy() thread_data["group_id"] = get_group_id_for_user(user, discussion_settings) serializer = ThreadSerializer(data=thread_data, context=context) actions_form = ThreadActionsForm(thread_data) if not (serializer.is_valid() and actions_form.is_valid()): raise ValidationError(dict(list(serializer.errors.items()) + list(actions_form.errors.items()))) serializer.save() cc_thread = serializer.instance thread_created.send(sender=None, user=user, post=cc_thread) api_thread = serializer.data _do_extra_actions(api_thread, cc_thread, list(thread_data.keys()), actions_form, context, request) track_thread_created_event(request, course, cc_thread, actions_form.cleaned_data["following"]) return api_thread
def setUp(self): super().setUp() self.user = UserFactory() self.course1 = CourseOverviewFactory() self.course2 = CourseOverviewFactory( id=CourseKey.from_string('{}a'.format(self.course1.id))) self.eligible_cert = GeneratedCertificateFactory.create( status=CertificateStatuses.downloadable, user=self.user, course_id=self.course1.id) self.ineligible_cert = GeneratedCertificateFactory.create( status=CertificateStatuses.audit_passing, user=self.user, course_id=self.course2.id)
def get_filename_safe_course_id(course_id, replacement_char='_'): """ Create a representation of a course_id that can be used safely in a filepath. """ try: course_key = CourseKey.from_string(course_id) filename = unicode(replacement_char).join( [course_key.org, course_key.course, course_key.run]) except InvalidKeyError: # If the course_id doesn't parse, we will still return a value here. filename = course_id # The safest characters are A-Z, a-z, 0-9, <underscore>, <period> and <hyphen>. # We represent the first four with \w. # TODO: Once we support courses with unicode characters, we will need to revisit this. return re.sub(r'[^\w\.\-]', unicode(replacement_char), filename)
def setUp(self): super(EligibleCertificateManagerTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments self.user = UserFactory() self.course1 = CourseOverviewFactory() self.course2 = CourseOverviewFactory( id=CourseKey.from_string('{}a'.format(self.course1.id))) self.eligible_cert = GeneratedCertificateFactory.create( status=CertificateStatuses.downloadable, user=self.user, course_id=self.course1.id) self.ineligible_cert = GeneratedCertificateFactory.create( status=CertificateStatuses.audit_passing, user=self.user, course_id=self.course2.id)
def registration_code_details(request, course_id): """ Post handler to mark the registration code as 1) valid 2) invalid 3) Unredeem. """ course_key = CourseKey.from_string(course_id) code = request.POST.get('registration_code') action_type = request.POST.get('action_type') course = get_course_by_id(course_key, depth=0) action_type_messages = { 'invalidate_registration_code': _('This enrollment code has been canceled. It can no longer be used.'), 'unredeem_registration_code': _('This enrollment code has been marked as unused.'), 'validate_registration_code': _('The enrollment code has been restored.') } try: registration_code = CourseRegistrationCode.objects.get(code=code) except CourseRegistrationCode.DoesNotExist: return JsonResponse({ 'message': _(u'The enrollment code ({code}) was not found for the {course_name} course.').format( code=code, course_name=course.display_name )}, status=400) if action_type == 'invalidate_registration_code': registration_code.is_valid = False registration_code.save() if RegistrationCodeRedemption.is_registration_code_redeemed(code): code_redemption = RegistrationCodeRedemption.get_registration_code_redemption(code, course_key) delete_redemption_entry(request, code_redemption, course_key) if action_type == 'validate_registration_code': registration_code.is_valid = True registration_code.save() if action_type == 'unredeem_registration_code': code_redemption = RegistrationCodeRedemption.get_registration_code_redemption(code, course_key) if code_redemption is None: return JsonResponse({ 'message': _(u'The redemption does not exist against enrollment code ({code}).').format( code=code)}, status=400) delete_redemption_entry(request, code_redemption, course_key) return JsonResponse({'message': action_type_messages[action_type]})
def registration_code_details(request, course_id): """ Post handler to mark the registration code as 1) valid 2) invalid 3) Unredeem. """ course_key = CourseKey.from_string(course_id) code = request.POST.get('registration_code') action_type = request.POST.get('action_type') course = get_course_by_id(course_key, depth=0) action_type_messages = { 'invalidate_registration_code': _('This enrolment code has been canceled. It can no longer be used.'), 'unredeem_registration_code': _('This enrolment code has been marked as unused.'), 'validate_registration_code': _('The enrolment code has been restored.') } try: registration_code = CourseRegistrationCode.objects.get(code=code) except CourseRegistrationCode.DoesNotExist: return JsonResponse({ 'message': _('The enrolment code ({code}) was not found for the {course_name} course.').format( code=code, course_name=course.display_name )}, status=400) if action_type == 'invalidate_registration_code': registration_code.is_valid = False registration_code.save() if RegistrationCodeRedemption.is_registration_code_redeemed(code): code_redemption = RegistrationCodeRedemption.get_registration_code_redemption(code, course_key) delete_redemption_entry(request, code_redemption, course_key) if action_type == 'validate_registration_code': registration_code.is_valid = True registration_code.save() if action_type == 'unredeem_registration_code': code_redemption = RegistrationCodeRedemption.get_registration_code_redemption(code, course_key) if code_redemption is None: return JsonResponse({ 'message': _('The redemption does not exist against enrolment code ({code}).').format( code=code)}, status=400) delete_redemption_entry(request, code_redemption, course_key) return JsonResponse({'message': action_type_messages[action_type]})
def test_list_libraries(self): """ Test that we can GET /library/ to list all libraries visible to the current user. """ # Create some more libraries libraries = [LibraryFactory.create() for _ in range(3)] lib_dict = dict([(lib.location.library_key, lib) for lib in libraries]) response = self.client.get_json(LIBRARY_REST_URL) self.assertEqual(response.status_code, 200) lib_list = parse_json(response) self.assertEqual(len(lib_list), len(libraries)) for entry in lib_list: self.assertIn("library_key", entry) self.assertIn("display_name", entry) key = CourseKey.from_string(entry["library_key"]) self.assertIn(key, lib_dict) self.assertEqual(entry["display_name"], lib_dict[key].display_name) del lib_dict[key] # To ensure no duplicates are matched
def setUp(self): super(EligibleCertificateManagerTest, self).setUp() self.user = UserFactory() self.course1 = CourseOverviewFactory() self.course2 = CourseOverviewFactory( id=CourseKey.from_string('{}a'.format(self.course1.id)) ) self.eligible_cert = GeneratedCertificateFactory.create( status=CertificateStatuses.downloadable, user=self.user, course_id=self.course1.id ) self.ineligible_cert = GeneratedCertificateFactory.create( status=CertificateStatuses.audit_passing, user=self.user, course_id=self.course2.id )
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': six.text_type(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 test_user_cannot_enroll_in_unknown_course_run_id( self, mock_get_course_runs): fake_course_str = str(self.course.id) + 'fake' fake_course_key = CourseKey.from_string(fake_course_str) course_entitlement = CourseEntitlementFactory.create(user=self.user) mock_get_course_runs.return_value = self.return_values url = reverse(self.ENTITLEMENTS_ENROLLMENT_NAMESPACE, args=[str(course_entitlement.uuid)]) data = {'course_run_id': str(fake_course_key)} response = self.client.post( url, data=json.dumps(data), content_type='application/json', ) expected_message = 'The Course Run ID is not a match for this Course Entitlement.' assert response.status_code == 400 assert response.data['message'] == expected_message # pylint: disable=no-member assert not CourseEnrollment.is_enrolled(self.user, fake_course_key)
def test_user_cannot_enroll_in_unknown_course_run_id(self, mock_get_course_runs): fake_course_str = str(self.course.id) + 'fake' fake_course_key = CourseKey.from_string(fake_course_str) course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED) mock_get_course_runs.return_value = self.return_values url = reverse( self.ENTITLEMENTS_ENROLLMENT_NAMESPACE, args=[str(course_entitlement.uuid)] ) data = { 'course_run_id': str(fake_course_key) } response = self.client.post( url, data=json.dumps(data), content_type='application/json', ) expected_message = 'The Course Run ID is not a match for this Course Entitlement.' assert response.status_code == 400 assert response.data['message'] == expected_message # pylint: disable=no-member assert not CourseEnrollment.is_enrolled(self.user, fake_course_key)
def validate_course_id(course_id): """ Validate course id and return the corresponding CourseKey object. Arguments: course_id (str): Course identifier Raises: (forms.ValidationError): if Given course jey is not valid or does not point to an existing course. Returns: (CourseKey): CourseKey object against the given course identifier. """ try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: msg = u'Course id invalid. Entered course id was: "{0}".'.format(course_id) raise forms.ValidationError(msg) if not modulestore().has_course(course_key): msg = u'Course not found. Entered course id was: "{0}".'.format(text_type(course_key)) raise forms.ValidationError(msg) return course_key
def add_coupon(request, course_id): """ add coupon in the Coupons Table """ code = request.POST.get('code') # check if the code is already in the Coupons Table and active try: course_id = CourseKey.from_string(course_id) coupon = Coupon.objects.get(is_active=True, code=code, course_id=course_id) except Coupon.DoesNotExist: # check if the coupon code is in the CourseRegistrationCode Table course_registration_code = CourseRegistrationCode.objects.filter( code=code) if course_registration_code: return JsonResponse( { 'message': _(u"The code ({code}) that you have tried to define is already in use as a registration code" ).format(code=code) }, status=400) # status code 400: Bad Request description = request.POST.get('description') course_id = request.POST.get('course_id') try: discount = int(request.POST.get('discount')) except ValueError: return JsonResponse( { 'message': _("Please Enter the Integer Value for Coupon Discount") }, status=400) # status code 400: Bad Request if discount > 100 or discount < 0: return JsonResponse( { 'message': _("Please Enter the Coupon Discount Value Less than or Equal to 100" ) }, status=400) # status code 400: Bad Request expiration_date = None if request.POST.get('expiration_date'): expiration_date = request.POST.get('expiration_date') try: expiration_date = datetime.datetime.strptime( expiration_date, "%m/%d/%Y").replace( tzinfo=pytz.UTC) + datetime.timedelta(days=1) except ValueError: return JsonResponse( { 'message': _("Please enter the date in this format i-e month/day/year" ) }, status=400) # status code 400: Bad Request coupon = Coupon(code=code, description=description, course_id=course_id, percentage_discount=discount, created_by_id=request.user.id, expiration_date=expiration_date) coupon.save() return JsonResponse({ 'message': _(u"coupon with the coupon code ({code}) added successfully"). format(code=code) }) if coupon: return JsonResponse( { 'message': _(u"coupon with the coupon code ({code}) already exists for this course" ).format(code=code) }, status=400) # status code 400: Bad Request