Skip to content

Commit dc95ac2

Browse files
feat(enterprise) ENT-11235: Fix EnterpriseCustomerAdmin invited_date and add invitation lifecycle fields
- Fixed the invited_date signal handling for EnterpriseCustomerAdmin - Updated related models and signals - Added date fields to track enterprise user invitation lifecycle
1 parent e0f0be3 commit dc95ac2

File tree

8 files changed

+158
-3
lines changed

8 files changed

+158
-3
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.5.8] - 2025-12-16
21+
---------------------
22+
* feat: Update admin table with new fields
23+
2024
[6.5.7] - 2025-11-28
2125
---------------------
2226
* feat: fetch SAP user id by remote_id_field_name

enterprise/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
Your project description goes here.
33
"""
44

5-
__version__ = "6.5.7"
5+
__version__ = "6.5.8"

enterprise/api/v1/serializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2047,7 +2047,7 @@ class Meta:
20472047
is_admin = serializers.SerializerMethodField()
20482048

20492049
def is_enterprise_customer_user(self, obj):
2050-
return hasattr(obj, 'user_id') and obj.user_id > 0
2050+
return hasattr(obj, 'user_id') and obj.user_id > 0
20512051

20522052
def get_enterprise_customer_user(self, obj):
20532053
"""
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Generated by Django 5.2.8 on 2025-12-29 17:47
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="invited_date",
16+
field=models.DateTimeField(
17+
blank=True,
18+
help_text="Timestamp when the admin was invited.",
19+
null=True
20+
),
21+
),
22+
migrations.AddField(
23+
model_name="enterprisecustomeradmin",
24+
name="joined_date",
25+
field=models.DateTimeField(
26+
blank=True,
27+
help_text="Timestamp when the admin accepted the invite and was created.",
28+
null=True,
29+
),
30+
),
31+
migrations.AddField(
32+
model_name="historicalpendingenterprisecustomeradminuser",
33+
name="invited_date",
34+
field=models.DateTimeField(
35+
blank=True,
36+
help_text="Timestamp when the admin invite was created.",
37+
null=True,
38+
),
39+
),
40+
migrations.AddField(
41+
model_name="pendingenterprisecustomeradminuser",
42+
name="invited_date",
43+
field=models.DateTimeField(
44+
blank=True,
45+
help_text="Timestamp when the admin invite was created.",
46+
null=True,
47+
),
48+
),
49+
]

enterprise/models.py

Lines changed: 17 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()
@@ -3971,6 +3970,12 @@ class PendingEnterpriseCustomerAdminUser(TimeStampedModel):
39713970
user_email = models.EmailField(null=False, blank=False)
39723971
history = HistoricalRecords()
39733972

3973+
invited_date = models.DateTimeField(
3974+
null=True,
3975+
blank=True,
3976+
help_text="Timestamp when the admin invite was created."
3977+
)
3978+
39743979
class Meta:
39753980
app_label = 'enterprise'
39763981
ordering = ['created']
@@ -5006,6 +5011,17 @@ class EnterpriseCustomerAdmin(TimeStampedModel):
50065011
default=False,
50075012
help_text=_("Whether the admin has completed the onboarding tour.")
50085013
)
5014+
invited_date = models.DateTimeField(
5015+
null=True,
5016+
blank=True,
5017+
help_text="Timestamp when the admin was invited."
5018+
)
5019+
5020+
joined_date = models.DateTimeField(
5021+
null=True,
5022+
blank=True,
5023+
help_text="Timestamp when the admin accepted the invite and was created."
5024+
)
50095025

50105026
class Meta:
50115027
app_label = 'enterprise'

enterprise/signals.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,16 @@
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 (
18+
EnterpriseCustomerAdmin,
19+
PendingEnterpriseCustomerAdminUser,
20+
)
1621
from enterprise.tasks import create_enterprise_enrollment
1722
from enterprise.utils import (
1823
NotConnectedToOpenEdX,
@@ -441,6 +446,27 @@ def generate_default_orchestration_record_display_name(sender, instance, **kwarg
441446
).count()
442447
instance.display_name = f'SSO-config-{instance.identity_provider}-{num_records_for_customer + 1}'
443448

449+
@receiver(post_save, sender=EnterpriseCustomerAdmin)
450+
def populate_admin_onboarding_metadata(sender, instance, created, **kwargs):
451+
"""
452+
Populate joined_date and invited metadata for enterprise admins.
453+
"""
454+
if not created:
455+
return
456+
457+
# joined_date = when admin record is created
458+
instance.joined_date = now()
459+
460+
# Attempt to backfill invitation metadata
461+
pending_invite = PendingEnterpriseCustomerAdminUser.objects.filter(
462+
enterprise_customer=instance.enterprise_customer_user.enterprise_customer,
463+
user_email=instance.enterprise_customer_user.user_email,
464+
).order_by("-invited_date").first()
465+
466+
if pending_invite:
467+
instance.invited_date = pending_invite.invited_date
468+
469+
instance.save(update_fields=["joined_date", "invited_date"])
444470

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

tests/test_enterprise/test_signals.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212

1313
from django.db import transaction
1414
from django.test import TestCase, override_settings
15+
from django.utils import timezone
1516

1617
from enterprise.constants import ENTERPRISE_ADMIN_ROLE, ENTERPRISE_LEARNER_ROLE
1718
from enterprise.models import (
1819
EnterpriseCourseEnrollment,
20+
EnterpriseCustomer,
1921
EnterpriseCustomerAdmin,
2022
EnterpriseCustomerCatalog,
2123
EnterpriseCustomerUser,
@@ -415,6 +417,48 @@ def test_delete_pending_enterprise_admin_user(self):
415417
).delete()
416418
self._assert_pending_ecus_exist(should_exist=False)
417419

420+
@mark.django_db
421+
class TestEnterpriseCustomerAdminSignals(unittest.TestCase):
422+
"""
423+
Tests signals related to EnterpriseCustomerAdmin creation and field updates.
424+
"""
425+
426+
def setUp(self):
427+
self.admin_user = UserFactory(email='user@example.com')
428+
self.enterprise_customer = EnterpriseCustomerFactory()
429+
430+
self.ecu = EnterpriseCustomerUserFactory(
431+
user_fk=self.admin_user,
432+
user_id=self.admin_user.id,
433+
enterprise_customer=self.enterprise_customer,
434+
)
435+
super().setUp()
436+
437+
def test_invited_date_is_set_on_creation(self):
438+
pending_admin = PendingEnterpriseCustomerAdminUserFactory(
439+
enterprise_customer=self.ecu.enterprise_customer,
440+
user_email=self.ecu.user_email,
441+
invited_date=timezone.now() - timedelta(days=1)
442+
)
443+
444+
admin = EnterpriseCustomerAdmin.objects.create(
445+
enterprise_customer_user=self.ecu
446+
)
447+
448+
admin.refresh_from_db()
449+
self.assertIsNotNone(admin.invited_date)
450+
451+
def test_joined_date_not_set_on_create(self):
452+
admin = EnterpriseCustomerAdmin(
453+
enterprise_customer_user=self.ecu
454+
)
455+
self.assertIsNone(getattr(admin, 'joined_date', None))
456+
457+
def test_joined_date_set_when_admin_user_is_accepted(self):
458+
admin = EnterpriseCustomerAdmin.objects.create(
459+
enterprise_customer_user=self.ecu
460+
)
461+
self.assertIsNotNone(admin.joined_date)
418462

419463
@mark.django_db
420464
@ddt.ddt

tests/test_utilities.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from enterprise import utils, validators
2121
from enterprise.models import (
2222
EnterpriseCustomer,
23+
EnterpriseCustomerAdmin,
2324
EnterpriseCustomerBrandingConfiguration,
2425
EnterpriseCustomerIdentityProvider,
2526
EnterpriseCustomerUser,
@@ -207,6 +208,21 @@ def setUp(self):
207208
"user_fk",
208209
]
209210
),
211+
(
212+
EnterpriseCustomerAdmin,
213+
[
214+
"uuid",
215+
"created",
216+
"modified",
217+
"enterprise_customer_user",
218+
"last_login",
219+
"onboarding_tour_dismissed",
220+
"onboarding_tour_completed",
221+
"invited_date",
222+
"joined_date",
223+
"completed_tour_flows",
224+
]
225+
),
210226
(
211227
EnterpriseCustomerBrandingConfiguration,
212228
[

0 commit comments

Comments
 (0)