diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index e7351a1da2..655f1378d2 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -31,6 +31,7 @@ from openedx.core.djangoapps.embargo import api as embargo_api from openedx.core.djangoapps.programs.utils import ProgramDataExtender, ProgramProgressMeter from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace from openedx.features.content_type_gating.models import ContentTypeGatingConfig +from openedx.features.course_duration_limits.models import CourseDurationLimitConfig from student.models import CourseEnrollment from util.db import outer_atomic from xmodule.modulestore.django import modulestore @@ -192,6 +193,10 @@ class ChooseModeView(View): user=request.user, course_key=course_key ), + "course_duration_limit_enabled": CourseDurationLimitConfig.enabled_for_enrollment( + user=request.user, + course_key=course_key + ), } context.update( get_experiment_user_metadata_context( diff --git a/lms/templates/course_modes/choose.html b/lms/templates/course_modes/choose.html index 068b89919b..7859ab228b 100644 --- a/lms/templates/course_modes/choose.html +++ b/lms/templates/course_modes/choose.html @@ -86,7 +86,7 @@ from openedx.core.djangolib.markup import HTML, Text
% if has_credit_upsell: - % if content_gating_enabled: + % if content_gating_enabled or course_duration_limit_enabled:

${_("Pursue Academic Credit with the Verified Track")}

% else:

${_("Pursue Academic Credit with a Verified Certificate")}

@@ -97,12 +97,16 @@ from openedx.core.djangolib.markup import HTML, Text

- % if content_gating_enabled: + % if content_gating_enabled or course_duration_limit_enabled:

${_("Benefits of the Verified Track")}

  • ${Text(_("{b_start}Eligible for credit:{b_end} Receive academic credit after successfully completing the course")).format(**b_tag_kwargs)}
  • -
  • ${Text(_("{b_start}Unlimited Course Access: {b_end}Learn at your own pace, and access materials anytime to brush up on what you've learned.")).format(**b_tag_kwargs)}
  • -
  • ${Text(_("{b_start}Graded Assignments: {b_end}Build your skills through graded assignments and projects.")).format(**b_tag_kwargs)}
  • + % if course_duration_limit_enabled: +
  • ${Text(_("{b_start}Unlimited Course Access: {b_end}Learn at your own pace, and access materials anytime to brush up on what you've learned.")).format(**b_tag_kwargs)}
  • + % endif + % if content_gating_enabled: +
  • ${Text(_("{b_start}Graded Assignments: {b_end}Build your skills through graded assignments and projects.")).format(**b_tag_kwargs)}
  • + % endif
  • ${Text(_("{b_start}Easily Sharable: {b_end}Add the certificate to your CV or resume, or post it directly on LinkedIn.")).format(**b_tag_kwargs)}
% else: @@ -126,7 +130,7 @@ from openedx.core.djangolib.markup import HTML, Text

% else: - % if content_gating_enabled: + % if content_gating_enabled or course_duration_limit_enabled:

${_("Pursue the Verified Track")}

% else:

${_("Pursue a Verified Certificate")}

@@ -138,11 +142,15 @@ from openedx.core.djangolib.markup import HTML, Text

- % if content_gating_enabled: + % if content_gating_enabled or course_duration_limit_enabled:

${_("Benefits of the Verified Track")}

    -
  • ${Text(_("{b_start}Unlimited Course Access: {b_end}Learn at your own pace, and access materials anytime to brush up on what you've learned.")).format(**b_tag_kwargs)}
  • -
  • ${Text(_("{b_start}Graded Assignments: {b_end}Build your skills through graded assignments and projects.")).format(**b_tag_kwargs)}
  • + % if course_duration_limit_enabled: +
  • ${Text(_("{b_start}Unlimited Course Access: {b_end}Learn at your own pace, and access materials anytime to brush up on what you've learned.")).format(**b_tag_kwargs)}
  • + % endif + % if content_gating_enabled: +
  • ${Text(_("{b_start}Graded Assignments: {b_end}Build your skills through graded assignments and projects.")).format(**b_tag_kwargs)}
  • + % endif
  • ${Text(_("{b_start}Easily Sharable: {b_end}Add the certificate to your CV or resume, or post it directly on LinkedIn.")).format(**b_tag_kwargs)}
% else: @@ -158,7 +166,7 @@ from openedx.core.djangolib.markup import HTML, Text
  • - % if content_gating_enabled: + % if content_gating_enabled or course_duration_limit_enabled: % else: @@ -205,8 +213,12 @@ from openedx.core.djangolib.markup import HTML, Text

    ${_("Audit This Course (No Certificate)")}

    ## Translators: b_start notes the beginning of a section of text bolded for emphasis, and b_end marks the end of the bolded text. - % if content_gating_enabled: + % if content_gating_enabled and course_duration_limit_enabled:

    ${Text(_("Audit this course for free and have access to course materials and discussions forums. {b_start}This track does not include graded assignments, or unlimited course access.{b_end}")).format(**b_tag_kwargs)}

    + % elif content_gating_enabled and not course_duration_limit_enabled: +

    ${Text(_("Audit this course for free and have access to course materials and discussions forums. {b_start}This track does not include graded assignments.{b_end}")).format(**b_tag_kwargs)}

    + % elif not content_gating_enabled and course_duration_limit_enabled: +

    ${Text(_("Audit this course for free and have access to course materials and discussions forums. {b_start}This track does not include unlimited course access.{b_end}")).format(**b_tag_kwargs)}

    % else:

    ${Text(_("Audit this course for free and have complete access to all the course material, activities, tests, and forums. {b_start}Please note that this track does not offer a certificate for learners who earn a passing grade.{b_end}")).format(**b_tag_kwargs)}

    % endif diff --git a/openedx/core/djangoapps/config_model_utils/utils.py b/openedx/core/djangoapps/config_model_utils/utils.py new file mode 100644 index 0000000000..eae11e1814 --- /dev/null +++ b/openedx/core/djangoapps/config_model_utils/utils.py @@ -0,0 +1,25 @@ +"""utils for feature-based enrollments""" +from experiments.models import ExperimentData +from openedx.features.course_duration_limits.config import ( + EXPERIMENT_ID, + EXPERIMENT_DATA_HOLDBACK_KEY +) + + +def is_in_holdback(user): + """ + Return true if given user is in holdback expermiment + """ + in_holdback = False + if user and user.is_authenticated: + try: + holdback_value = ExperimentData.objects.get( + user=user, + experiment_id=EXPERIMENT_ID, + key=EXPERIMENT_DATA_HOLDBACK_KEY, + ).value + in_holdback = holdback_value == 'True' + except ExperimentData.DoesNotExist: + pass + + return in_holdback diff --git a/openedx/features/content_type_gating/models.py b/openedx/features/content_type_gating/models.py index c608a670f1..17e97dace1 100644 --- a/openedx/features/content_type_gating/models.py +++ b/openedx/features/content_type_gating/models.py @@ -12,14 +12,12 @@ from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from lms.djangoapps.courseware.masquerade import get_course_masquerade, is_masquerading_as_specific_student -from experiments.models import ExperimentData from openedx.core.djangoapps.config_model_utils.models import StackedConfigurationModel +from openedx.core.djangoapps.config_model_utils.utils import is_in_holdback from openedx.features.content_type_gating.helpers import has_staff_roles from openedx.features.course_duration_limits.config import ( CONTENT_TYPE_GATING_FLAG, FEATURE_BASED_ENROLLMENT_GLOBAL_KILL_FLAG, - EXPERIMENT_ID, - EXPERIMENT_DATA_HOLDBACK_KEY ) from student.models import CourseEnrollment @@ -109,18 +107,7 @@ class ContentTypeGatingConfig(StackedConfigurationModel): return False # check if user is in holdback - is_in_holdback = False - if user and user.is_authenticated and (user_variable_represents_correct_user): - try: - holdback_value = ExperimentData.objects.get( - user=user, - experiment_id=EXPERIMENT_ID, - key=EXPERIMENT_DATA_HOLDBACK_KEY, - ).value - is_in_holdback = holdback_value == 'True' - except ExperimentData.DoesNotExist: - pass - if is_in_holdback: + if user_variable_represents_correct_user and is_in_holdback(user): return False # enrollment might be None if the user isn't enrolled. In that case, diff --git a/openedx/features/course_duration_limits/models.py b/openedx/features/course_duration_limits/models.py index 7f8f76a550..783c632dd1 100644 --- a/openedx/features/course_duration_limits/models.py +++ b/openedx/features/course_duration_limits/models.py @@ -13,20 +13,18 @@ from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from course_modes.models import CourseMode -from experiments.models import ExperimentData from lms.djangoapps.courseware.masquerade import ( get_course_masquerade, get_masquerade_role, is_masquerading_as_specific_student ) from openedx.core.djangoapps.config_model_utils.models import StackedConfigurationModel +from openedx.core.djangoapps.config_model_utils.utils import is_in_holdback from openedx.features.content_type_gating.helpers import has_staff_roles from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID, CONTENT_TYPE_GATE_GROUP_IDS from openedx.features.course_duration_limits.config import ( CONTENT_TYPE_GATING_FLAG, FEATURE_BASED_ENROLLMENT_GLOBAL_KILL_FLAG, - EXPERIMENT_ID, - EXPERIMENT_DATA_HOLDBACK_KEY ) from student.models import CourseEnrollment from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID @@ -130,20 +128,10 @@ class CourseDurationLimitConfig(StackedConfigurationModel): return False no_masquerade = get_course_masquerade(user, course_key) is None - is_in_holdback = False student_masquerade = is_masquerading_as_specific_student(user, course_key) - # TODO: clean up as part of REV-100 - if user and user.username and (no_masquerade or student_masquerade): - try: - holdback_value = ExperimentData.objects.get( - user=user, - experiment_id=EXPERIMENT_ID, - key=EXPERIMENT_DATA_HOLDBACK_KEY, - ).value - is_in_holdback = holdback_value == 'True' - except ExperimentData.DoesNotExist: - pass - if is_in_holdback: + + # check if user is in holdback + if (no_masquerade or student_masquerade) and is_in_holdback(user): return False # enrollment might be None if the user isn't enrolled. In that case, diff --git a/openedx/features/course_duration_limits/tests/test_models.py b/openedx/features/course_duration_limits/tests/test_models.py index 723c0dedda..6a6db087cd 100644 --- a/openedx/features/course_duration_limits/tests/test_models.py +++ b/openedx/features/course_duration_limits/tests/test_models.py @@ -80,9 +80,9 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase): user = self.user course_key = self.course_overview.id - query_count = 8 - if not pass_enrollment: - query_count = 9 + query_count = 9 + if pass_enrollment and already_enrolled: + query_count = 8 with self.assertNumQueries(query_count): enabled = CourseDurationLimitConfig.enabled_for_enrollment( diff --git a/themes/edx.org/lms/templates/course_modes/choose.html b/themes/edx.org/lms/templates/course_modes/choose.html index 3b82606259..7cf8349647 100644 --- a/themes/edx.org/lms/templates/course_modes/choose.html +++ b/themes/edx.org/lms/templates/course_modes/choose.html @@ -154,7 +154,7 @@ from openedx.features.portfolio_project import INCLUDE_PORTFOLIO_UPSELL_MODAL
    % if has_credit_upsell: - % if content_gating_enabled: + % if content_gating_enabled or course_duration_limit_enabled:

    ${_("Pursue Academic Credit with the Verified Track")}

    % else:

    ${_("Pursue Academic Credit with a Verified Certificate")}

    @@ -165,12 +165,16 @@ from openedx.features.portfolio_project import INCLUDE_PORTFOLIO_UPSELL_MODAL

    - % if content_gating_enabled: + % if content_gating_enabled or course_duration_limit_enabled:

    ${_("Benefits of the Verified Track")}

    • ${Text(_("{b_start}Eligible for credit:{b_end} Receive academic credit after successfully completing the course")).format(**b_tag_kwargs)}
    • -
    • ${Text(_("{b_start}Unlimited Course Access: {b_end}Learn at your own pace, and access materials anytime to brush up on what you've learned.")).format(**b_tag_kwargs)}
    • -
    • ${Text(_("{b_start}Graded Assignments: {b_end}Build your skills through graded assignments and projects.")).format(**b_tag_kwargs)}
    • + % if course_duration_limit_enabled: +
    • ${Text(_("{b_start}Unlimited Course Access: {b_end}Learn at your own pace, and access materials anytime to brush up on what you've learned.")).format(**b_tag_kwargs)}
    • + % endif + % if content_gating_enabled: +
    • ${Text(_("{b_start}Graded Assignments: {b_end}Build your skills through graded assignments and projects.")).format(**b_tag_kwargs)}
    • + % endif
    • ${Text(_("{b_start}Easily Sharable: {b_end}Add the certificate to your CV or resume, or post it directly on LinkedIn.")).format(**b_tag_kwargs)}
    • ${Text(_("{b_start}Support our Mission: {b_end}EdX, a non-profit, relies on verified certificates to help fund affordable education to everyone globally.")).format(**b_tag_kwargs)}
    @@ -195,7 +199,7 @@ from openedx.features.portfolio_project import INCLUDE_PORTFOLIO_UPSELL_MODAL

    % else: - % if content_gating_enabled: + % if content_gating_enabled or course_duration_limit_enabled:

    ${_("Pursue the Verified Track")}

    % else:

    ${_("Pursue a Verified Certificate")}

    @@ -205,11 +209,15 @@ from openedx.features.portfolio_project import INCLUDE_PORTFOLIO_UPSELL_MODAL

    - % if content_gating_enabled: + % if content_gating_enabled or course_duration_limit_enabled:

    ${_("Benefits of the Verified Track")}

      -
    • ${Text(_("{b_start}Unlimited Course Access: {b_end}Learn at your own pace, and access materials anytime to brush up on what you've learned.")).format(**b_tag_kwargs)}
    • -
    • ${Text(_("{b_start}Graded Assignments: {b_end}Build your skills through graded assignments and projects.")).format(**b_tag_kwargs)}
    • + % if course_duration_limit_enabled: +
    • ${Text(_("{b_start}Unlimited Course Access: {b_end}Learn at your own pace, and access materials anytime to brush up on what you've learned.")).format(**b_tag_kwargs)}
    • + % endif + % if content_gating_enabled: +
    • ${Text(_("{b_start}Graded Assignments: {b_end}Build your skills through graded assignments and projects.")).format(**b_tag_kwargs)}
    • + % endif
    • ${Text(_("{b_start}Easily Sharable: {b_end}Add the certificate to your CV or resume, or post it directly on LinkedIn.")).format(**b_tag_kwargs)}
    • ${Text(_("{b_start}Support our Mission: {b_end}EdX, a non-profit, relies on verified certificates to help fund affordable education to everyone globally.")).format(**b_tag_kwargs)}
    @@ -229,7 +237,7 @@ from openedx.features.portfolio_project import INCLUDE_PORTFOLIO_UPSELL_MODAL
    • - % if content_gating_enabled: + % if content_gating_enabled or course_duration_limit_enabled: % else: @@ -315,8 +323,12 @@ from openedx.features.portfolio_project import INCLUDE_PORTFOLIO_UPSELL_MODAL

      ${_("Audit This Course (No Certificate)")}

      ## Translators: b_start notes the beginning of a section of text bolded for emphasis, and b_end marks the end of the bolded text. - % if content_gating_enabled: + % if content_gating_enabled and course_duration_limit_enabled:

      ${Text(_("Audit this course for free and have access to course materials and discussions forums. {b_start}This track does not include graded assignments, or unlimited course access.{b_end}")).format(**b_tag_kwargs)}

      + % elif content_gating_enabled and not course_duration_limit_enabled: +

      ${Text(_("Audit this course for free and have access to course materials and discussions forums. {b_start}This track does not include graded assignments.{b_end}")).format(**b_tag_kwargs)}

      + % elif not content_gating_enabled and course_duration_limit_enabled: +

      ${Text(_("Audit this course for free and have access to course materials and discussions forums. {b_start}This track does not include unlimited course access.{b_end}")).format(**b_tag_kwargs)}

      % else:

      ${Text(_("Audit this course for free and have complete access to all the course material, activities, tests, and forums. {b_start}Please note that this track does not offer a certificate for learners who earn a passing grade.{b_end}")).format(**b_tag_kwargs)}

      % endif