Skip to content

Commit

Permalink
Merge pull request #10 from open-craft/0x29a/bb7878/cherry-pick-edx-e…
Browse files Browse the repository at this point in the history
…nterprise-customizations-to-palm

feat: cherry-pick edx-enteprise customizations to Palm [BB-7878]
  • Loading branch information
Agrendalath authored Nov 7, 2023
2 parents 68f55aa + fcd515b commit 55e5012
Show file tree
Hide file tree
Showing 28 changed files with 792 additions and 128 deletions.
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches: [master]
pull_request:
branches: [master]

concurrency:
group: ci-${{ github.event.pull_request.number || github.ref }}
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/mysql8-migrations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ jobs:
key: ${{ runner.os }}-pip-${{ hashFiles('requirements/pip_tools.txt') }}
restore-keys: ${{ runner.os }}-pip-

- name: Downgrade pip to work around https://github.com/mitsuhiko/rye/issues/368
run: |
pip install --upgrade pip==23.1
- name: Ubuntu and sql Versions
run: |
lsb_release -a
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ Unreleased
----------
* Nothing

[3.61.12]
---------
This version is based on v3.61.11 and contains some backports needed to OpenCraft's clients. There is no "official" v3.61.12.
See this PR for more details: https://github.com/open-craft/edx-enterprise/pull/10

[3.61.11]
---------
feat: include owners and longer descriptions for degreed2 content metadata transmissions
Expand Down
2 changes: 1 addition & 1 deletion enterprise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
Your project description goes here.
"""

__version__ = "3.61.11"
__version__ = "3.61.12"

default_app_config = "enterprise.apps.EnterpriseConfig"
8 changes: 8 additions & 0 deletions enterprise/admin/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ class ManageLearnersForm(forms.Form):
label=_("Enroll these learners in this course"), required=False,
help_text=_("To enroll learners in a course, enter a course ID."),
)
force_enrollment = forms.BooleanField(
label=_("Force Enrollment"),
help_text=_("The selected course is 'Invite Only'. Only staff can enroll learners to this course."),
required=False,
)
course_mode = forms.ChoiceField(
label=_("Course enrollment track"), required=False,
choices=BLANK_CHOICE_DASH + [
Expand Down Expand Up @@ -130,6 +135,7 @@ class Fields:
REASON = "reason"
SALES_FORCE_ID = "sales_force_id"
DISCOUNT = "discount"
FORCE_ENROLLMENT = "force_enrollment"

class CsvColumns:
"""
Expand Down Expand Up @@ -394,6 +400,8 @@ class Meta:
"enable_audit_data_reporting",
"replace_sensitive_sso_username",
"hide_course_original_price",
"hide_course_price_when_zero",
"allow_enrollment_in_invite_only_courses",
"enable_portal_code_management_screen",
"enable_portal_subscription_management_screen",
"enable_learner_portal",
Expand Down
12 changes: 9 additions & 3 deletions enterprise/admin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,8 @@ def _enroll_users(
notify=True,
enrollment_reason=None,
sales_force_id=None,
discount=0.0
discount=0.0,
force_enrollment=False,
):
"""
Enroll the users with the given email addresses to the course.
Expand All @@ -689,6 +690,7 @@ def _enroll_users(
mode: The enrollment mode the users will be enrolled in the course with
course_id: The ID of the course in which we want to enroll
notify: Whether to notify (by email) the users that have been enrolled
force_enrollment: Force enrollment into "Invite Only" courses
"""
pending_messages = []
paid_modes = ['verified', 'professional']
Expand All @@ -702,6 +704,7 @@ def _enroll_users(
enrollment_reason=enrollment_reason,
discount=discount,
sales_force_id=sales_force_id,
force_enrollment=force_enrollment,
)
all_successes = succeeded + pending
if notify:
Expand Down Expand Up @@ -818,6 +821,7 @@ def post(self, request, customer_uuid):
sales_force_id = manage_learners_form.cleaned_data.get(ManageLearnersForm.Fields.SALES_FORCE_ID)
course_mode = manage_learners_form.cleaned_data.get(ManageLearnersForm.Fields.COURSE_MODE)
course_id = None
force_enrollment = manage_learners_form.cleaned_data.get(ManageLearnersForm.Fields.FORCE_ENROLLMENT)

if not course_id_with_emails:
course_details = manage_learners_form.cleaned_data.get(ManageLearnersForm.Fields.COURSE) or {}
Expand All @@ -832,7 +836,8 @@ def post(self, request, customer_uuid):
notify=notify,
enrollment_reason=manual_enrollment_reason,
sales_force_id=sales_force_id,
discount=discount
discount=discount,
force_enrollment=force_enrollment,
)
else:
for course_id, emails in course_id_with_emails.items():
Expand All @@ -847,7 +852,8 @@ def post(self, request, customer_uuid):
notify=notify,
enrollment_reason=manual_enrollment_reason,
sales_force_id=sales_force_id,
discount=discount
discount=discount,
force_enrollment=force_enrollment,
)

# Redirect to GET if everything went smooth.
Expand Down
32 changes: 31 additions & 1 deletion enterprise/api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from django.contrib import auth

from enterprise.models import EnterpriseCustomerUser, SystemWideEnterpriseUserRoleAssignment
from enterprise.models import EnterpriseCustomer, EnterpriseCustomerUser, SystemWideEnterpriseUserRoleAssignment

User = auth.get_user_model()

Expand All @@ -33,6 +33,36 @@ def filter_queryset(self, request, queryset, view):
return queryset


class EnterpriseCourseEnrollmentFilterBackend(filters.BaseFilterBackend):
"""
Filter backend to return enrollments under the user's enterprise(s) only.
* Staff users will bypass this filter.
* Non-staff users will receive enrollments under their linked enterprises,
only if they have the `enterprise.can_enroll_learners` permission.
* Non-staff users without the `enterprise.can_enroll_learners` permission
will receive only their own enrollments.
"""

def filter_queryset(self, request, queryset, view):
"""
Filter out enrollments if learner is not linked
"""

if request.user.is_staff:
return queryset

if request.user.has_perm('enterprise.can_enroll_learners'):
enterprise_customers = EnterpriseCustomer.objects.filter(enterprise_customer_users__user_id=request.user.id)
return queryset.filter(enterprise_customer_user__enterprise_customer__in=enterprise_customers)

filter_kwargs = {
view.USER_ID_FILTER: request.user.id,
}

return queryset.filter(**filter_kwargs)


class EnterpriseCustomerUserFilterBackend(filters.BaseFilterBackend):
"""
Allow filtering on the enterprise customer user api endpoint.
Expand Down
27 changes: 26 additions & 1 deletion enterprise/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,10 +325,35 @@ class EnterpriseCourseEnrollmentReadOnlySerializer(serializers.ModelSerializer):
class Meta:
model = models.EnterpriseCourseEnrollment
fields = (
'enterprise_customer_user', 'course_id'
'enterprise_customer_user', 'course_id', 'created'
)


class EnterpriseCourseEnrollmentWithAdditionalFieldsReadOnlySerializer(EnterpriseCourseEnrollmentReadOnlySerializer):
"""
Serializer for EnterpriseCourseEnrollment model with additional fields.
"""

class Meta:
model = models.EnterpriseCourseEnrollment
fields = (
'enterprise_customer_user',
'course_id',
'created',
'enrollment_date',
'enrollment_track',
'user_email',
'course_start',
'course_end',
)

enrollment_track = serializers.CharField()
enrollment_date = serializers.DateTimeField()
user_email = serializers.EmailField()
course_start = serializers.DateTimeField()
course_end = serializers.DateTimeField()


class EnterpriseCourseEnrollmentWriteSerializer(serializers.ModelSerializer):
"""
Serializer for writing to the EnterpriseCourseEnrollment model.
Expand Down
6 changes: 4 additions & 2 deletions enterprise/api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

from enterprise import models
from enterprise.api.filters import (
EnterpriseCourseEnrollmentFilterBackend,
EnterpriseCustomerInviteKeyFilterBackend,
EnterpriseCustomerUserFilterBackend,
EnterpriseLinkedUserFilterBackend,
Expand Down Expand Up @@ -530,7 +531,8 @@ class EnterpriseCourseEnrollmentViewSet(EnterpriseReadWriteModelViewSet):
API views for the ``enterprise-course-enrollment`` API endpoint.
"""

queryset = models.EnterpriseCourseEnrollment.objects.all()
queryset = models.EnterpriseCourseEnrollment.with_additional_fields.all()
filter_backends = (filters.OrderingFilter, DjangoFilterBackend, EnterpriseCourseEnrollmentFilterBackend)

USER_ID_FILTER = 'enterprise_customer_user__user_id'
FIELDS = (
Expand All @@ -544,7 +546,7 @@ def get_serializer_class(self):
Use a special serializer for any requests that aren't read-only.
"""
if self.request.method in ('GET',):
return serializers.EnterpriseCourseEnrollmentReadOnlySerializer
return serializers.EnterpriseCourseEnrollmentWithAdditionalFieldsReadOnlySerializer
return serializers.EnterpriseCourseEnrollmentWriteSerializer


Expand Down
14 changes: 12 additions & 2 deletions enterprise/api_client/lms.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,15 @@ def has_course_mode(self, course_run_id, mode):
course_modes = self.get_course_modes(course_run_id)
return any(course_mode for course_mode in course_modes if course_mode['slug'] == mode)

def enroll_user_in_course(self, username, course_id, mode, cohort=None, enterprise_uuid=None):
def enroll_user_in_course(
self,
username,
course_id,
mode,
cohort=None,
enterprise_uuid=None,
force_enrollment=False,
):
"""
Call the enrollment API to enroll the user in the course specified by course_id.
Expand All @@ -138,6 +146,7 @@ def enroll_user_in_course(self, username, course_id, mode, cohort=None, enterpri
mode (str): The enrollment mode which should be used for the enrollment
cohort (str): Add the user to this named cohort
enterprise_uuid (str): Add course enterprise uuid
force_enrollment (bool): Force the enrollment even if course is Invite Only
Returns:
dict: A dictionary containing details of the enrollment, including course details, mode, username, etc.
Expand All @@ -152,7 +161,8 @@ def enroll_user_in_course(self, username, course_id, mode, cohort=None, enterpri
'is_active': True,
'mode': mode,
'cohort': cohort,
'enterprise_uuid': str(enterprise_uuid)
'enterprise_uuid': str(enterprise_uuid),
'force_enrollment': force_enrollment,
}
)
response.raise_for_status()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('enterprise', '0170_auto_20230301_1627'),
]

operations = [
migrations.AddField(
model_name='enterprisecustomer',
name='hide_course_price_when_zero',
field=models.BooleanField(default=False, help_text='Specify whether course cost should be hidden in the landing page when the final price is zero.'),
),
migrations.AddField(
model_name='historicalenterprisecustomer',
name='hide_course_price_when_zero',
field=models.BooleanField(default=False, help_text='Specify whether course cost should be hidden in the landing page when the final price is zero.'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('enterprise', '0171_adds_hide_course_price_when_zero_to_enterprisecustomer'),
]

operations = [
migrations.AddField(
model_name='enterprisecustomer',
name='allow_enrollment_in_invite_only_courses',
field=models.BooleanField(default=False, help_text="Specifies if learners are allowed to enroll into courses marked as 'invitation-only', when they attempt to enroll from the landing page."),
),
migrations.AddField(
model_name='historicalenterprisecustomer',
name='allow_enrollment_in_invite_only_courses',
field=models.BooleanField(default=False, help_text="Specifies if learners are allowed to enroll into courses marked as 'invitation-only', when they attempt to enroll from the landing page."),
),
]
Loading

0 comments on commit 55e5012

Please sign in to comment.