@@ -1,4 +1,3 @@
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from django.db import IntegrityError, transaction
|
||||
@@ -7,7 +6,6 @@ from django_filters.rest_framework import DjangoFilterBackend
|
||||
from edx_rest_framework_extensions.authentication import JwtAuthentication
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from pytz import UTC
|
||||
from rest_framework import permissions, viewsets, status
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.response import Response
|
||||
@@ -16,7 +14,7 @@ from entitlements.api.v1.filters import CourseEntitlementFilter
|
||||
from entitlements.api.v1.permissions import IsAdminOrAuthenticatedReadOnly
|
||||
from entitlements.api.v1.serializers import CourseEntitlementSerializer
|
||||
from entitlements.models import CourseEntitlement
|
||||
from entitlements.utils import is_course_run_entitlement_fullfillable
|
||||
from entitlements.utils import is_course_run_entitlement_fulfillable
|
||||
from lms.djangoapps.commerce.utils import refund_entitlement
|
||||
from openedx.core.djangoapps.catalog.utils import get_course_runs_for_course
|
||||
from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticationCrossDomainCsrf
|
||||
@@ -332,7 +330,7 @@ class EntitlementEnrollmentViewSet(viewsets.GenericViewSet):
|
||||
)
|
||||
|
||||
# Verify that the run is fullfillable
|
||||
if not is_course_run_entitlement_fullfillable(course_run_key, entitlement):
|
||||
if not is_course_run_entitlement_fulfillable(course_run_key, entitlement):
|
||||
return Response(
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
data={
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
1. Course UUID Retrieved from Discovery by API
|
||||
----------------------------------------------
|
||||
|
||||
Status
|
||||
------
|
||||
|
||||
Accepted
|
||||
|
||||
Context
|
||||
-------
|
||||
|
||||
Course UUID is a more reliable and consistently unique identifier for a Course.
|
||||
|
||||
|
||||
Decision
|
||||
--------
|
||||
|
||||
The decision was made for consistency to not move the course UUID into the Platform data model. As a result the only
|
||||
method available to get a Course UUID based on a Course Key is the Discovery Service.
|
||||
|
||||
Consequences
|
||||
------------
|
||||
|
||||
When there is a need to find a Course by UUID, but only the Course Key is available the Discovery API is required to
|
||||
resolve the identifier.
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
* https://openedx.atlassian.net/wiki/spaces/LEARNER/pages/171180253/Program+Bundling
|
||||
@@ -1,16 +1,25 @@
|
||||
"""Entitlement Models"""
|
||||
|
||||
import logging
|
||||
import uuid as uuid_tools
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db import models
|
||||
from django.db import transaction
|
||||
from django.utils.timezone import now
|
||||
from model_utils.models import TimeStampedModel
|
||||
|
||||
from lms.djangoapps.certificates.models import GeneratedCertificate
|
||||
from openedx.core.djangoapps.catalog.utils import get_course_uuid_for_course
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from student.models import CourseEnrollment
|
||||
from student.models import CourseEnrollmentException
|
||||
from util.date_utils import strftime_localized
|
||||
from entitlements.utils import is_course_run_entitlement_fulfillable
|
||||
|
||||
log = logging.getLogger("common.entitlements.models")
|
||||
|
||||
|
||||
class CourseEntitlementPolicy(models.Model):
|
||||
@@ -320,6 +329,91 @@ class CourseEntitlement(TimeStampedModel):
|
||||
enrollment_course_run=None
|
||||
).select_related('user').select_related('enrollment_course_run')
|
||||
|
||||
@classmethod
|
||||
def get_fulfillable_entitlements(cls, user):
|
||||
"""
|
||||
Returns all fulfillable entitlements for a User
|
||||
|
||||
Arguments:
|
||||
user (User): The user we are looking at the entitlements of.
|
||||
|
||||
Returns
|
||||
Queryset: A queryset of course Entitlements ordered descending by creation date that a user can enroll in.
|
||||
These must not be expired and not have a course run already assigned to it.
|
||||
"""
|
||||
|
||||
return cls.objects.filter(
|
||||
user=user,
|
||||
).exclude(
|
||||
expired_at__isnull=False,
|
||||
enrollment_course_run__isnull=False
|
||||
).order_by('-created')
|
||||
|
||||
@classmethod
|
||||
def get_fulfillable_entitlement_for_user_course_run(cls, user, course_run_key):
|
||||
"""
|
||||
Retrieves a fulfillable entitlement for the user and the given course run.
|
||||
|
||||
Arguments:
|
||||
user (User): The user that we are inspecting the entitlements for.
|
||||
course_run_key (CourseKey): The course run Key.
|
||||
|
||||
Returns:
|
||||
CourseEntitlement: The most recent fulfillable CourseEntitlement, None otherwise.
|
||||
"""
|
||||
# Check if the User has any fulfillable entitlements.
|
||||
# Note: Wait to retrieve the Course UUID until we have confirmed the User has fulfillable entitlements.
|
||||
# This was done to avoid calling the APIs when the User does not have an entitlement.
|
||||
entitlements = cls.get_fulfillable_entitlements(user)
|
||||
if entitlements:
|
||||
course_uuid = get_course_uuid_for_course(course_run_key)
|
||||
if course_uuid:
|
||||
entitlement = entitlements.filter(course_uuid=course_uuid).first()
|
||||
if is_course_run_entitlement_fulfillable(course_run_key=course_run_key, entitlement=entitlement):
|
||||
return entitlement
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
@transaction.atomic
|
||||
def enroll_user_and_fulfill_entitlement(cls, entitlement, course_run_key):
|
||||
"""
|
||||
Enrolls the user in the Course Run and updates the entitlement with the new Enrollment.
|
||||
|
||||
Returns:
|
||||
bool: True if successfully fulfills given entitlement by enrolling the user in the given course run.
|
||||
"""
|
||||
try:
|
||||
enrollment = CourseEnrollment.enroll(
|
||||
user=entitlement.user,
|
||||
course_key=course_run_key,
|
||||
mode=entitlement.mode
|
||||
)
|
||||
except CourseEnrollmentException:
|
||||
log.exception('Login for Course Entitlement {uuid} failed'.format(uuid=entitlement.uuid))
|
||||
return False
|
||||
|
||||
entitlement.set_enrollment(enrollment)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def check_for_existing_entitlement_and_enroll(cls, user, course_run_key):
|
||||
"""
|
||||
Looks at the User's existing entitlements to see if the user already has a Course Entitlement for the
|
||||
course run provided in the course_key. If the user does have an Entitlement with no run set, the User is
|
||||
enrolled in the mode set in the Entitlement.
|
||||
|
||||
Arguments:
|
||||
user (User): The user that we are inspecting the entitlements for.
|
||||
course_run_key (CourseKey): The course run Key.
|
||||
Returns:
|
||||
bool: True if the user had an eligible course entitlement to which an enrollment in the
|
||||
given course run was applied.
|
||||
"""
|
||||
entitlement = cls.get_fulfillable_entitlement_for_user_course_run(user, course_run_key)
|
||||
if entitlement:
|
||||
return cls.enroll_user_and_fulfill_entitlement(entitlement, course_run_key)
|
||||
return False
|
||||
|
||||
|
||||
class CourseEntitlementSupportDetail(TimeStampedModel):
|
||||
"""
|
||||
|
||||
@@ -6,16 +6,88 @@ from datetime import timedelta
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
from mock import patch
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from lms.djangoapps.certificates.models import CertificateStatuses # pylint: disable=import-error
|
||||
from course_modes.models import CourseMode
|
||||
from course_modes.tests.factories import CourseModeFactory
|
||||
from lms.djangoapps.certificates.api import MODES
|
||||
from lms.djangoapps.certificates.models import CertificateStatuses
|
||||
from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||
from student.tests.factories import CourseEnrollmentFactory
|
||||
from student.models import CourseEnrollment
|
||||
from student.tests.factories import (TEST_PASSWORD, CourseEnrollmentFactory, UserFactory)
|
||||
|
||||
# Entitlements is not in CMS' INSTALLED_APPS so these imports will error during test collection
|
||||
if settings.ROOT_URLCONF == 'lms.urls':
|
||||
from entitlements.tests.factories import CourseEntitlementFactory
|
||||
from entitlements.models import CourseEntitlement
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class TestCourseEntitlementModelHelpers(ModuleStoreTestCase):
|
||||
"""
|
||||
Series of tests for the helper methods in the CourseEntitlement Model Class.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestCourseEntitlementModelHelpers, self).setUp()
|
||||
self.user = UserFactory()
|
||||
self.client.login(username=self.user.username, password=TEST_PASSWORD)
|
||||
|
||||
@patch("entitlements.models.get_course_uuid_for_course")
|
||||
def test_check_for_existing_entitlement_and_enroll(self, mock_get_course_uuid):
|
||||
course = CourseFactory()
|
||||
CourseModeFactory(
|
||||
course_id=course.id,
|
||||
mode_slug=CourseMode.VERIFIED,
|
||||
# This must be in the future to ensure it is returned by downstream code.
|
||||
expiration_datetime=now() + timedelta(days=1)
|
||||
)
|
||||
entitlement = CourseEntitlementFactory.create(
|
||||
mode=CourseMode.VERIFIED,
|
||||
user=self.user,
|
||||
)
|
||||
mock_get_course_uuid.return_value = entitlement.course_uuid
|
||||
|
||||
assert not CourseEnrollment.is_enrolled(user=self.user, course_key=course.id)
|
||||
|
||||
CourseEntitlement.check_for_existing_entitlement_and_enroll(
|
||||
user=self.user,
|
||||
course_run_key=course.id,
|
||||
)
|
||||
|
||||
assert CourseEnrollment.is_enrolled(user=self.user, course_key=course.id)
|
||||
|
||||
entitlement.refresh_from_db()
|
||||
assert entitlement.enrollment_course_run
|
||||
|
||||
@patch("entitlements.models.get_course_uuid_for_course")
|
||||
def test_check_for_no_entitlement_and_do_not_enroll(self, mock_get_course_uuid):
|
||||
course = CourseFactory()
|
||||
CourseModeFactory(
|
||||
course_id=course.id,
|
||||
mode_slug=CourseMode.VERIFIED,
|
||||
# This must be in the future to ensure it is returned by downstream code.
|
||||
expiration_datetime=now() + timedelta(days=1)
|
||||
)
|
||||
entitlement = CourseEntitlementFactory.create(
|
||||
mode=CourseMode.VERIFIED,
|
||||
user=self.user,
|
||||
)
|
||||
mock_get_course_uuid.return_value = None
|
||||
|
||||
assert not CourseEnrollment.is_enrolled(user=self.user, course_key=course.id)
|
||||
|
||||
CourseEntitlement.check_for_existing_entitlement_and_enroll(
|
||||
user=self.user,
|
||||
course_run_key=course.id,
|
||||
)
|
||||
|
||||
assert not CourseEnrollment.is_enrolled(user=self.user, course_key=course.id)
|
||||
|
||||
entitlement.refresh_from_db()
|
||||
assert entitlement.enrollment_course_run is None
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@@ -28,6 +100,8 @@ class TestModels(TestCase):
|
||||
start=now()
|
||||
)
|
||||
self.enrollment = CourseEnrollmentFactory.create(course_id=self.course.id)
|
||||
self.user = UserFactory()
|
||||
self.client.login(username=self.user.username, password=TEST_PASSWORD)
|
||||
|
||||
def test_is_entitlement_redeemable(self):
|
||||
"""
|
||||
|
||||
@@ -16,13 +16,13 @@ from student.tests.factories import (TEST_PASSWORD, UserFactory, CourseOverviewF
|
||||
# Entitlements is not in CMS' INSTALLED_APPS so these imports will error during test collection
|
||||
if settings.ROOT_URLCONF == 'lms.urls':
|
||||
from entitlements.tests.factories import CourseEntitlementFactory
|
||||
from entitlements.utils import is_course_run_entitlement_fullfillable
|
||||
from entitlements.utils import is_course_run_entitlement_fulfillable
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
class TestCourseRunFullfillableForEntitlement(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the utility function is_course_run_entitlement_fullfillable
|
||||
Tests for the utility function is_course_run_entitlement_fulfillable
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
@@ -64,7 +64,7 @@ class TestCourseRunFullfillableForEntitlement(ModuleStoreTestCase):
|
||||
|
||||
entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED)
|
||||
|
||||
assert is_course_run_entitlement_fullfillable(course_overview.id, entitlement)
|
||||
assert is_course_run_entitlement_fulfillable(course_overview.id, entitlement)
|
||||
|
||||
def test_course_run_not_fullfillable_run_ended(self):
|
||||
course_overview = self.create_course(
|
||||
@@ -76,7 +76,7 @@ class TestCourseRunFullfillableForEntitlement(ModuleStoreTestCase):
|
||||
|
||||
entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED)
|
||||
|
||||
assert not is_course_run_entitlement_fullfillable(course_overview.id, entitlement)
|
||||
assert not is_course_run_entitlement_fulfillable(course_overview.id, entitlement)
|
||||
|
||||
def test_course_run_not_fullfillable_enroll_period_ended(self):
|
||||
course_overview = self.create_course(
|
||||
@@ -88,7 +88,7 @@ class TestCourseRunFullfillableForEntitlement(ModuleStoreTestCase):
|
||||
|
||||
entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED)
|
||||
|
||||
assert not is_course_run_entitlement_fullfillable(course_overview.id, entitlement)
|
||||
assert not is_course_run_entitlement_fulfillable(course_overview.id, entitlement)
|
||||
|
||||
def test_course_run_fullfillable_user_enrolled(self):
|
||||
course_overview = self.create_course(
|
||||
@@ -102,7 +102,7 @@ class TestCourseRunFullfillableForEntitlement(ModuleStoreTestCase):
|
||||
# Enroll User in the Course, but do not update the entitlement
|
||||
CourseEnrollmentFactory.create(user=entitlement.user, course_id=course_overview.id)
|
||||
|
||||
assert is_course_run_entitlement_fullfillable(course_overview.id, entitlement)
|
||||
assert is_course_run_entitlement_fulfillable(course_overview.id, entitlement)
|
||||
|
||||
def test_course_run_not_fullfillable_upgrade_ended(self):
|
||||
course_overview = self.create_course(
|
||||
@@ -115,4 +115,4 @@ class TestCourseRunFullfillableForEntitlement(ModuleStoreTestCase):
|
||||
|
||||
entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED)
|
||||
|
||||
assert not is_course_run_entitlement_fullfillable(course_overview.id, entitlement)
|
||||
assert not is_course_run_entitlement_fulfillable(course_overview.id, entitlement)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from course_modes.models import CourseMode
|
||||
from django.utils import timezone
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
|
||||
|
||||
def is_course_run_entitlement_fullfillable(course_run_id, entitlement, compare_date=timezone.now()):
|
||||
def is_course_run_entitlement_fulfillable(course_run_key, entitlement, compare_date=timezone.now()):
|
||||
"""
|
||||
Checks that the current run meets the following criteria for an entitlement
|
||||
|
||||
@@ -13,14 +13,14 @@ def is_course_run_entitlement_fullfillable(course_run_id, entitlement, compare_d
|
||||
3) A User can upgrade to the entitlement mode
|
||||
|
||||
Arguments:
|
||||
course_run_id (String): The id of the Course run that is being checked.
|
||||
course_run_key (CourseKey): The id of the Course run that is being checked.
|
||||
entitlement: The Entitlement that we are checking against.
|
||||
compare_date: The date and time that we are comparing against. Defaults to timezone.now()
|
||||
|
||||
Returns:
|
||||
bool: True if the Course Run is fullfillable for the CourseEntitlement.
|
||||
"""
|
||||
course_overview = CourseOverview.get_from_id(course_run_id)
|
||||
course_overview = CourseOverview.get_from_id(course_run_key)
|
||||
|
||||
# Verify that the course is still running
|
||||
run_start = course_overview.start
|
||||
@@ -36,7 +36,7 @@ def is_course_run_entitlement_fullfillable(course_run_id, entitlement, compare_d
|
||||
)
|
||||
|
||||
# Ensure the course run is upgradeable and the mode matches the entitlement's mode
|
||||
unexpired_paid_modes = [mode.slug for mode in CourseMode.paid_modes_for_course(course_run_id)]
|
||||
unexpired_paid_modes = [mode.slug for mode in CourseMode.paid_modes_for_course(course_run_key)]
|
||||
can_upgrade = unexpired_paid_modes and entitlement.mode in unexpired_paid_modes
|
||||
|
||||
return is_running and can_upgrade and can_enroll
|
||||
|
||||
@@ -9,16 +9,20 @@ import uuid
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
|
||||
import analytics
|
||||
import dogstats_wrapper as dog_stats_api
|
||||
from bulk_email.models import Optout
|
||||
from courseware.courses import get_courses, sort_by_announcement, sort_by_start_date
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import authenticate, load_backend, login as django_login, logout
|
||||
from django.contrib.auth import login as django_login
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.auth.views import password_reset_confirm
|
||||
from django.core import mail
|
||||
from django.core.urlresolvers import NoReverseMatch, reverse, reverse_lazy
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.validators import ValidationError, validate_email
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db import transaction
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import Signal, receiver
|
||||
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
|
||||
@@ -26,12 +30,15 @@ from django.shortcuts import redirect
|
||||
from django.template.context_processors import csrf
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
from django.utils.http import base36_to_int, is_safe_url, urlencode, urlsafe_base64_encode
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.http import base36_to_int, urlsafe_base64_encode
|
||||
from django.utils.translation import get_language, ungettext
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
|
||||
from django.views.decorators.http import require_GET, require_POST
|
||||
from eventtracking import tracker
|
||||
from ipware.ip import get_ip
|
||||
# Note that this lives in LMS, so this dependency should be refactored.
|
||||
from notification_prefs.views import enable_notifications
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from pytz import UTC
|
||||
@@ -39,19 +46,14 @@ from requests import HTTPError
|
||||
from six import text_type, iteritems
|
||||
from social_core.exceptions import AuthAlreadyAssociated, AuthException
|
||||
from social_django import utils as social_utils
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
import analytics
|
||||
import dogstats_wrapper as dog_stats_api
|
||||
import openedx.core.djangoapps.external_auth.views
|
||||
import third_party_auth
|
||||
import track.views
|
||||
from bulk_email.models import Optout # pylint: disable=import-error
|
||||
from course_modes.models import CourseMode
|
||||
from courseware.courses import get_courses, sort_by_announcement, sort_by_start_date # pylint: disable=import-error
|
||||
from edxmako.shortcuts import render_to_response, render_to_string
|
||||
from eventtracking import tracker
|
||||
# Note that this lives in LMS, so this dependency should be refactored.
|
||||
from notification_prefs.views import enable_notifications
|
||||
from entitlements.models import CourseEntitlement
|
||||
from openedx.core.djangoapps import monitoring_utils
|
||||
from openedx.core.djangoapps.catalog.utils import (
|
||||
get_programs_with_type,
|
||||
@@ -103,7 +105,6 @@ from util.bad_request_rate_limiter import BadRequestRateLimiter
|
||||
from util.db import outer_atomic
|
||||
from util.json_request import JsonResponse
|
||||
from util.password_policy_validators import validate_password_length, validate_password_strength
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
log = logging.getLogger("edx.student")
|
||||
|
||||
@@ -403,6 +404,9 @@ def change_enrollment(request, check_access=True):
|
||||
if redirect_url:
|
||||
return HttpResponse(redirect_url)
|
||||
|
||||
if CourseEntitlement.check_for_existing_entitlement_and_enroll(user=user, course_run_key=course_id):
|
||||
return HttpResponse(reverse('courseware', args=[unicode(course_id)]))
|
||||
|
||||
# Check that auto enrollment is allowed for this course
|
||||
# (= the course is NOT behind a paywall)
|
||||
if CourseMode.can_auto_enroll(course_id):
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
""" API v0 views. """
|
||||
import logging
|
||||
|
||||
from courseware import courses
|
||||
from django.core.urlresolvers import reverse
|
||||
from edx_rest_api_client import exceptions
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
@@ -11,16 +13,15 @@ from rest_framework.views import APIView
|
||||
from six import text_type
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from courseware import courses
|
||||
from enrollment.api import add_enrollment
|
||||
from enrollment.views import EnrollmentCrossDomainSessionAuth
|
||||
from entitlements.models import CourseEntitlement
|
||||
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
|
||||
from openedx.core.djangoapps.embargo import api as embargo_api
|
||||
from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in
|
||||
from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser
|
||||
from student.models import CourseEnrollment
|
||||
from util.json_request import JsonResponse
|
||||
|
||||
from ...constants import Messages
|
||||
from ...http import DetailResponse
|
||||
|
||||
@@ -113,6 +114,14 @@ class BasketsView(APIView):
|
||||
honor_mode = CourseMode.mode_for_course(course_key, CourseMode.HONOR)
|
||||
audit_mode = CourseMode.mode_for_course(course_key, CourseMode.AUDIT)
|
||||
|
||||
# Check to see if the User has an entitlement and enroll them if they have one for this course
|
||||
if CourseEntitlement.check_for_existing_entitlement_and_enroll(user=user, course_run_key=course_key):
|
||||
return JsonResponse(
|
||||
{
|
||||
'redirect_destination': reverse('courseware', args=[unicode(course_id)]),
|
||||
},
|
||||
)
|
||||
|
||||
# Accept either honor or audit as an enrollment mode to
|
||||
# maintain backwards compatibility with existing courses
|
||||
default_enrollment_mode = audit_mode or honor_mode
|
||||
|
||||
@@ -41,12 +41,15 @@
|
||||
this.redirect(redirectUrl);
|
||||
}
|
||||
}
|
||||
}).done(function() {
|
||||
}).done(function(response) {
|
||||
// If we successfully enrolled, redirect the user
|
||||
// to the next page (usually the student dashboard or payment flow)
|
||||
if (redirectUrl) {
|
||||
if (response.redirect_destination) {
|
||||
this.redirect(response.redirect_destination)
|
||||
} else if (redirectUrl) {
|
||||
this.redirect(redirectUrl);
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import copy
|
||||
import datetime
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
import pycountry
|
||||
from django.conf import settings
|
||||
@@ -11,13 +12,13 @@ from edx_rest_api_client.client import EdxRestApiClient
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from pytz import UTC
|
||||
|
||||
from entitlements.utils import is_course_run_entitlement_fullfillable
|
||||
from student.models import CourseEnrollment
|
||||
from entitlements.utils import is_course_run_entitlement_fulfillable
|
||||
from openedx.core.djangoapps.catalog.cache import (PROGRAM_CACHE_KEY_TPL,
|
||||
SITE_PROGRAM_UUIDS_CACHE_KEY_TPL)
|
||||
from openedx.core.djangoapps.catalog.models import CatalogIntegration
|
||||
from openedx.core.lib.edx_api_utils import get_edx_api_data
|
||||
from openedx.core.lib.token_utils import JwtBuilder
|
||||
from student.models import CourseEnrollment
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -287,6 +288,69 @@ def get_course_runs_for_course(course_uuid):
|
||||
return []
|
||||
|
||||
|
||||
def get_course_uuid_for_course(course_run_key):
|
||||
"""
|
||||
Retrieve the Course UUID for a given course key
|
||||
|
||||
Arguments:
|
||||
course_run_key (CourseKey): A Key for a Course run that will be pulled apart to get just the information
|
||||
required for a Course (e.g. org+course)
|
||||
|
||||
Returns:
|
||||
UUID: Course UUID and None if it was not retrieved.
|
||||
"""
|
||||
catalog_integration = CatalogIntegration.current()
|
||||
|
||||
if catalog_integration.is_enabled():
|
||||
try:
|
||||
user = catalog_integration.get_service_user()
|
||||
except ObjectDoesNotExist:
|
||||
logger.error(
|
||||
'Catalog service user with username [%s] does not exist. Course UUID will not be retrieved.',
|
||||
catalog_integration.service_username,
|
||||
)
|
||||
return []
|
||||
|
||||
api = create_catalog_api_client(user)
|
||||
|
||||
run_cache_key = '{base}.course_run.{course_run_key}'.format(
|
||||
base=catalog_integration.CACHE_KEY,
|
||||
course_run_key=course_run_key
|
||||
)
|
||||
|
||||
course_run_data = get_edx_api_data(
|
||||
catalog_integration,
|
||||
'course_runs',
|
||||
resource_id=unicode(course_run_key),
|
||||
api=api,
|
||||
cache_key=run_cache_key if catalog_integration.is_cache_enabled else None,
|
||||
long_term_cache=True,
|
||||
many=False,
|
||||
)
|
||||
|
||||
course_key_str = course_run_data.get('course', None)
|
||||
|
||||
if course_key_str:
|
||||
run_cache_key = '{base}.course.{course_key}'.format(
|
||||
base=catalog_integration.CACHE_KEY,
|
||||
course_key=course_key_str
|
||||
)
|
||||
|
||||
data = get_edx_api_data(
|
||||
catalog_integration,
|
||||
'courses',
|
||||
resource_id=course_key_str,
|
||||
api=api,
|
||||
cache_key=run_cache_key if catalog_integration.is_cache_enabled else None,
|
||||
long_term_cache=True,
|
||||
many=False,
|
||||
)
|
||||
uuid_str = data.get('uuid', None)
|
||||
if uuid_str:
|
||||
return uuid.UUID(uuid_str)
|
||||
return None
|
||||
|
||||
|
||||
def get_pseudo_session_for_entitlement(entitlement):
|
||||
"""
|
||||
This function is used to pass pseudo-data to the front end, returning a single session, regardless of whether that
|
||||
@@ -325,7 +389,7 @@ def get_fulfillable_course_runs_for_entitlement(entitlement, course_runs):
|
||||
for course_run in course_runs:
|
||||
course_id = CourseKey.from_string(course_run.get('key'))
|
||||
is_enrolled = CourseEnrollment.is_enrolled(entitlement.user, course_id)
|
||||
if is_course_run_entitlement_fullfillable(course_id, entitlement, search_time):
|
||||
if is_course_run_entitlement_fulfillable(course_id, entitlement, search_time):
|
||||
if (is_enrolled and
|
||||
entitlement.enrollment_course_run and
|
||||
course_id == entitlement.enrollment_course_run.course_id):
|
||||
|
||||
Reference in New Issue
Block a user