Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 unreleased

[8.14.0] - 2026-09-16
----------------------
* feat: Add ``ActivationEmailEnterpriseContextEnricher`` pipeline step for the
``AccountActivationEmailContextGenerated`` filter (ENT-11816)

[8.13.0] - 2026-09-14
----------------------
* Changed ``SupportEnterpriseEnrollmentDataInjector`` to accept/return the full enrollments list
Expand Down
2 changes: 1 addition & 1 deletion enterprise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Your project description goes here.
"""

__version__ = "8.13.0"
__version__ = "8.14.0"
31 changes: 31 additions & 0 deletions enterprise/filters/logistration.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
build_enterprise_branding_for_authn_mfe,
get_enterprise_slug_login_url,
handle_enterprise_cookies_for_logistration,
is_enterprise_learner,
update_logistration_context_for_enterprise,
)
except ImportError:
Expand All @@ -41,6 +42,7 @@
build_enterprise_branding_for_authn_mfe = None
get_enterprise_slug_login_url = None
handle_enterprise_cookies_for_logistration = None
is_enterprise_learner = None
update_logistration_context_for_enterprise = None

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -300,3 +302,32 @@ def run_filter(self, redirect_url: str, user: Any) -> dict[str, Any]: # pylint:
return {'redirect_url': selection_url, 'user': user}

return {'redirect_url': redirect_url, 'user': user}


class ActivationEmailEnterpriseContextEnricher(PipelineStep):
"""
Adds ``is_enterprise_learner`` to the activation email's message context.

This step is intended to be registered as a pipeline step for the
``org.openedx.authentication.account_activation.email.context.generated.v1`` filter.
"""

def run_filter( # pylint: disable=arguments-differ
self, user: Any, message_context: dict[str, Any]
) -> dict[str, Any]:
"""
Add the enterprise learner flag to the activation email message context.

Arguments:
user (User): the Django User the activation email is being composed for.
message_context (dict): context dictionary used to render the activation email.

Returns:
dict: updated pipeline data with ``user`` and ``message_context`` keys.
"""
log.info(
"ActivationEmailEnterpriseContextEnricher running: user_id=%s",
user.id,
)
message_context['is_enterprise_learner'] = is_enterprise_learner(user)
return {"user": user, "message_context": message_context}
4 changes: 4 additions & 0 deletions enterprise/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@
"fail_silently": False,
"pipeline": ["enterprise.filters.logistration.PostLoginEnterpriseRedirect"],
},
"org.openedx.authentication.account_activation.email.context.generated.v1": {
"fail_silently": False,
"pipeline": ["enterprise.filters.logistration.ActivationEmailEnterpriseContextEnricher"],
},
}


Expand Down
57 changes: 57 additions & 0 deletions tests/filters/test_logistration.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
from django.test import RequestFactory, TestCase

from enterprise.filters.logistration import (
ActivationEmailEnterpriseContextEnricher,
AuthnMFEEnterpriseContextEnricher,
LoginFormEnterpriseOverrides,
LogistrationViewEnterpriseContextEnricher,
LogistrationViewEnterpriseCookieSetter,
PostLoginEnterpriseRedirect,
RegistrationFormEnterpriseOverrides,
)
from enterprise.models import EnterpriseCustomerUser
from test_utils.factories import EnterpriseCustomerUserFactory, UserFactory


def _make_request():
Expand Down Expand Up @@ -510,3 +513,57 @@ def test_learner_data_api_error_propagates(self, _mock_learner_data):

with self.assertRaises(RuntimeError):
step.run_filter(redirect_url='/dashboard', user=self._make_user())


def _is_enterprise_learner_via_db(user):
"""
Stand-in for the platform's ``is_enterprise_learner``: a real DB lookup against
``EnterpriseCustomerUser`` rather than a canned boolean. The real function lives in
``openedx.features.enterprise_support.utils`` (ENT-11576 tracks migrating it into
edx-enterprise) and isn't importable outside a full LMS install, so it must still be
patched here — but the patched behavior is driven by real factory-created rows.
"""
return EnterpriseCustomerUser.objects.filter(user_id=user.id).exists()


_IS_ENTERPRISE_LEARNER_PATH = 'enterprise.filters.logistration.is_enterprise_learner'


class TestActivationEmailEnterpriseContextEnricher(TestCase):
"""
Tests for ActivationEmailEnterpriseContextEnricher pipeline step.
"""

def _make_step(self):
return ActivationEmailEnterpriseContextEnricher(
'org.openedx.authentication.account_activation.email.context.generated.v1', [],
)

@patch(_IS_ENTERPRISE_LEARNER_PATH, side_effect=_is_enterprise_learner_via_db)
def test_flags_enterprise_linked_user(self, _mock_is_enterprise_learner):
"""
A user linked to an enterprise customer gets is_enterprise_learner=True in the
message context.
"""
user = UserFactory()
EnterpriseCustomerUserFactory(user_id=user.id)
message_context = {'key': 'abc123'}

step = self._make_step()
result = step.run_filter(user=user, message_context=message_context)

assert result['message_context']['is_enterprise_learner'] is True
assert result['user'] is user

@patch(_IS_ENTERPRISE_LEARNER_PATH, side_effect=_is_enterprise_learner_via_db)
def test_does_not_flag_unlinked_user(self, _mock_is_enterprise_learner):
"""
A user with no enterprise link gets is_enterprise_learner=False in the message context.
"""
user = UserFactory()
message_context = {'key': 'abc123'}

step = self._make_step()
result = step.run_filter(user=user, message_context=message_context)

assert result['message_context']['is_enterprise_learner'] is False