Skip to content

Commit 9cce301

Browse files
feat(enterprise) ENT-11235: Fix EnterpriseCustomerAdmin invited_date and add invitation lifecycle fields
removed signal and set invited_date via defaults updating changelog.rst Removed unintended migration file fixed comments
1 parent c2712ef commit 9cce301

File tree

11 files changed

+248
-96
lines changed

11 files changed

+248
-96
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ Unreleased
1717
----------
1818
* nothing unreleased
1919

20+
[6.6.3] - 2026-01-22
21+
---------------------
22+
* feat: updates the enterprise customer user table
23+
2024
[6.6.2] - 2026-01-15
2125
---------------------
2226
* feat: add a waffle flag for invite admins

enterprise/api/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""
22
Python API for various enterprise functionality.
33
"""
4+
from django.utils.timezone import now
5+
46
from enterprise import roles_api
57
from enterprise.models import EnterpriseCustomerAdmin, PendingEnterpriseCustomerAdminUser
68

@@ -26,6 +28,7 @@ def activate_admin_permissions(enterprise_customer_user):
2628
# if this user is an admin, we want to create an accompanying EnterpriseCustomerAdmin record
2729
EnterpriseCustomerAdmin.objects.get_or_create(
2830
enterprise_customer_user=enterprise_customer_user,
31+
defaults={'joined_date': now()}
2932
)
3033
except PendingEnterpriseCustomerAdminUser.DoesNotExist:
3134
return # this is ok, nothing to do

enterprise/api/v1/views/enterprise_customer_admin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ def create_admin_by_email(self, request):
143143

144144
response_status_code = status.HTTP_200_OK
145145
admin, was_created = models.EnterpriseCustomerAdmin.objects.get_or_create(
146-
enterprise_customer_user=enterprise_customer_user
146+
enterprise_customer_user=enterprise_customer_user,
147+
defaults={'joined_date': timezone.now()}
147148
)
148149
if was_created:
149150
response_status_code = status.HTTP_201_CREATED

enterprise/management/commands/create_enterprise_customer_admins.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ def handle(self, *args, **options):
110110
admin_records = [
111111
EnterpriseCustomerAdmin(
112112
enterprise_customer_user=eu,
113-
last_login=timezone.now()
113+
last_login=timezone.now(),
114+
joined_date=timezone.now(),
114115
)
115116
for eu in enterprise_users_to_create
116117
]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 5.2.8 on 2026-01-30 05:50
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("enterprise", "0242_mariadb_uuid_conversion"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="enterprisecustomeradmin",
15+
name="joined_date",
16+
field=models.DateTimeField(
17+
blank=True,
18+
help_text="Timestamp when the admin accepted the invite and was created.",
19+
null=True,
20+
),
21+
),
22+
]

enterprise/models.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1091,7 +1091,6 @@ class EnterpriseCustomerUser(TimeStampedModel):
10911091
related_name='linked_enterprise_customer_users',
10921092
on_delete=models.SET_NULL
10931093
)
1094-
10951094
objects = EnterpriseCustomerUserManager()
10961095
all_objects = EnterpriseCustomerUserManager(linked_only=False)
10971096
history = HistoricalRecords()
@@ -5006,6 +5005,12 @@ class EnterpriseCustomerAdmin(TimeStampedModel):
50065005
default=False,
50075006
help_text=_("Whether the admin has completed the onboarding tour.")
50085007
)
5008+
#Track when the admin accepted the invite and joined.
5009+
joined_date = models.DateTimeField(
5010+
null=True,
5011+
blank=True,
5012+
help_text="Timestamp when the admin accepted the invite and was created."
5013+
)
50095014

50105015
class Meta:
50115016
app_label = 'enterprise'

enterprise/signals.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
from django.db import transaction
99
from django.db.models.signals import post_delete, post_save, pre_save
1010
from django.dispatch import receiver
11+
from django.utils.timezone import now
1112

1213
from enterprise import models, roles_api
1314
from enterprise.api import activate_admin_permissions
1415
from enterprise.api_client.enterprise_catalog import EnterpriseCatalogApiClient
1516
from enterprise.decorators import disable_for_loaddata
17+
from enterprise.models import EnterpriseCustomerAdmin, PendingEnterpriseCustomerAdminUser
1618
from enterprise.tasks import create_enterprise_enrollment
1719
from enterprise.utils import (
1820
NotConnectedToOpenEdX,
@@ -439,8 +441,8 @@ def generate_default_orchestration_record_display_name(sender, instance, **kwarg
439441
num_records_for_customer = models.EnterpriseCustomerSsoConfiguration.objects.filter(
440442
enterprise_customer=instance.enterprise_customer,
441443
).count()
442-
instance.display_name = f'SSO-config-{instance.identity_provider}-{num_records_for_customer + 1}'
443444

445+
instance.display_name = f'SSO-config-{instance.identity_provider}-{num_records_for_customer + 1}'
444446

445447
# Don't connect this receiver if we dont have access to CourseEnrollment model
446448
if CourseEnrollment is not None:

tests/test_enterprise/api/test_serializers.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from django.contrib.auth.models import Permission
1616
from django.http import HttpRequest
1717
from django.test import TestCase
18+
from django.urls import reverse
1819

1920
from enterprise.api.utils import CourseRunProgressStatuses
2021
from enterprise.api.v1.serializers import (
@@ -37,6 +38,7 @@
3738
SystemWideEnterpriseUserRoleAssignment,
3839
)
3940
from test_utils import FAKE_UUIDS, TEST_PGP_KEY, TEST_USERNAME, APITest, factories
41+
from rest_framework.test import APIClient, APITestCase
4042

4143
Application = get_application_model()
4244

@@ -573,6 +575,93 @@ def test_serialize_pending_users(self):
573575

574576
self.assertEqual(expected_pending_admin_user, serialized_pending_admin_user)
575577

578+
class TestEnterpriseCustomerMembersView(APITestCase):
579+
def setUp(self):
580+
self.client = APIClient()
581+
# Create an enterprise customer
582+
self.enterprise_customer = factories.EnterpriseCustomerFactory()
583+
584+
# Learner user
585+
self.learner_user = factories.UserFactory()
586+
factories.EnterpriseCustomerUserFactory(
587+
enterprise_customer=self.enterprise_customer,
588+
user_fk=self.learner_user,
589+
user_id=self.learner_user.id,
590+
)
591+
592+
# Admin user (should be excluded)
593+
self.admin_user = factories.UserFactory()
594+
595+
# Create admin role
596+
admin_role = SystemWideEnterpriseRole.objects.create(name="enterprise_admin")
597+
598+
# Assign the role to the admin user
599+
SystemWideEnterpriseUserRoleAssignment.objects.create(
600+
user_id=self.admin_user.id,
601+
role=admin_role,
602+
)
603+
604+
# Link the admin user to the enterprise customer
605+
factories.EnterpriseCustomerUserFactory(
606+
enterprise_customer=self.enterprise_customer,
607+
user_fk=self.admin_user,
608+
user_id=self.admin_user.id,
609+
)
610+
611+
def test_only_learners_returned(self):
612+
url = reverse(
613+
"enterprise-customer-members",
614+
kwargs={"enterprise_uuid": self.enterprise_customer.uuid}
615+
)
616+
response = self.client.get(url)
617+
self.assertEqual(response.status_code, 200)
618+
619+
# Extract user_ids from response
620+
returned_ids = [u['enterprise_customer_user']['user_id'] for u in response.data['results']]
621+
self.assertIn(self.learner_user.id, returned_ids)
622+
self.assertNotIn(self.admin_user.id, returned_ids)
623+
624+
def test_inactive_ecu_records_not_returned(self):
625+
"""
626+
Test that inactive EnterpriseCustomerUser records are excluded from the result set.
627+
"""
628+
# Create a learner role
629+
learner_role = SystemWideEnterpriseRole.objects.create(name="enterprise_learner")
630+
631+
# Create another learner user
632+
inactive_learner_user = factories.UserFactory()
633+
inactive_ecu = factories.EnterpriseCustomerUserFactory(
634+
enterprise_customer=self.enterprise_customer,
635+
user_fk=inactive_learner_user,
636+
user_id=inactive_learner_user.id,
637+
active=False, # Mark this user as inactive
638+
)
639+
640+
# Assign learner role to both users
641+
SystemWideEnterpriseUserRoleAssignment.objects.create(
642+
user_id=self.learner_user.id,
643+
role=learner_role,
644+
)
645+
SystemWideEnterpriseUserRoleAssignment.objects.create(
646+
user_id=inactive_learner_user.id,
647+
role=learner_role,
648+
)
649+
650+
url = reverse(
651+
"enterprise-customer-members",
652+
kwargs={"enterprise_uuid": self.enterprise_customer.uuid}
653+
)
654+
response = self.client.get(url)
655+
self.assertEqual(response.status_code, 200)
656+
657+
# Extract user_ids from response
658+
returned_ids = [u['enterprise_customer_user']['user_id'] for u in response.data['results']]
659+
660+
# Only the active learner should be returned
661+
self.assertIn(self.learner_user.id, returned_ids)
662+
# The inactive learner should NOT be returned
663+
self.assertNotIn(inactive_learner_user.id, returned_ids)
664+
576665

577666
class TestEnterpriseMembersSerializer(TestCase):
578667
"""

tests/test_enterprise/test_signals.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from enterprise.constants import ENTERPRISE_ADMIN_ROLE, ENTERPRISE_LEARNER_ROLE
1717
from enterprise.models import (
1818
EnterpriseCourseEnrollment,
19+
EnterpriseCustomer,
1920
EnterpriseCustomerAdmin,
2021
EnterpriseCustomerCatalog,
2122
EnterpriseCustomerUser,
@@ -416,6 +417,7 @@ def test_delete_pending_enterprise_admin_user(self):
416417
self._assert_pending_ecus_exist(should_exist=False)
417418

418419

420+
419421
@mark.django_db
420422
@ddt.ddt
421423
class TestEnterpriseAdminRoleSignals(unittest.TestCase):

0 commit comments

Comments
 (0)