-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds two models, `DefaultEnterpriseEnrollmentIntention` and `DefaultEnterpriseEnrollmentRealization`. The former defines, for a customer, a course/run that associated users should be automatically enrolled into. The latter represents the relationship between that intention record and a realized EnterpriseCourseEnrollment; persisting realized enrollments in this way will help to make read operations related to default enrollments much more efficient. ENT-9577
- Loading branch information
1 parent
ae70aa4
commit 4da6c93
Showing
4 changed files
with
277 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
============================== | ||
Default Enterprise Enrollments | ||
============================== | ||
|
||
Status | ||
====== | ||
Proposed - October 2024 | ||
|
||
Context | ||
======= | ||
Enterprise needs a solution for managing automated enrollments into "default" courses or specific course runs | ||
for enterprise learners. The solution needs to support customer-specific enrollment configurations | ||
without tightly coupling these configurations to a specific subsidy type, i.e. we should be able in the future | ||
to manage default enrollments via both learner credit and subscription subsidy types. | ||
|
||
Core requirements | ||
----------------- | ||
1. Internal staff should be able to configure one or more default enrollments with either a course | ||
or a specific course run for automatic enrollment. In the case of specifying a course, | ||
the default enrollment flow should cause the "realization" of default enrollments for learners | ||
into the currently-advertised, enrollable run for a course. | ||
2. Default Enrollments should be loosely coupled to subsidy type. | ||
3. Unenrollment: If a learner chooses to unenroll from a default course, they should not be automatically re-enrolled. | ||
4. Graceful handling of license revocation: Upon license revocation, we currently downgrade the learner’s | ||
enrollment mode to ``audit``. This fact should be visible from any new APIs exposed | ||
in the domain of default enrollments. | ||
5. Non-enrollable courses: If a course becomes unenrollable, default enrollments should fail gracefully, | ||
and in a way that's obvious to the learner. | ||
|
||
Decision | ||
======== | ||
We will implement two new models, ``DefaultEnterpriseEnrollmentIntention`` to represent the course/runs that | ||
learners should be default-enrolled into for a given enterprise, and ``DefaultEnterpriseEnrollmentRealization`` | ||
which represents the mapping between the intention and actual enrollment record(s) for the learner/customer. | ||
|
||
Qualities | ||
--------- | ||
1. Flexibility: The ``DefaultEnterpriseEnrollmentIntention`` model will allow specification of either a course | ||
or course run. | ||
2. Business logic: The API for this domain (future ADR) will implement the business logic around choosing | ||
the appropriate course run, for enforcing the applicability of a subsidy's specified catalog to the course, | ||
and the enrollability of the course. | ||
3. Non-Tightly Coupled to subsidy type: Nothing in the domain of default enrollments will persist data | ||
related to a subsidy (although a license or transaction identifier will ultimately become associated with | ||
an ``EnterpriseCourseEnrollment`` record during realization). | ||
|
||
Consequences | ||
============ | ||
1. It's a flexible design. | ||
2. It relies on a network call to enterprise-catalog to fetch content metadata (we can use caching to | ||
make this efficient). | ||
3. We're introducing more complexity in terms of how subsidized enterprise enrollments | ||
can come into existence. | ||
4. The realization model makes the provenance of default enrollments explicit and easy to examine. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# Generated by Django 4.2.15 on 2024-10-10 15:02 | ||
|
||
from django.conf import settings | ||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
import django.utils.timezone | ||
import model_utils.fields | ||
import simple_history.models | ||
import uuid | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
('enterprise', '0222_alter_enterprisegroup_group_type_and_more'), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='DefaultEnterpriseEnrollmentIntention', | ||
fields=[ | ||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), | ||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), | ||
('is_removed', models.BooleanField(default=False)), | ||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||
('content_type', models.CharField(choices=[('course', 'Course'), ('course_run', 'Course Run')], help_text='The type of content (e.g. a course vs. a course run).', max_length=127)), | ||
('content_key', models.CharField(help_text='A course or course run that related users should be automatically enrolled into.', max_length=255)), | ||
('enterprise_customer', models.ForeignKey(help_text='The customer for which this default enrollment will be realized.', on_delete=django.db.models.deletion.CASCADE, related_name='default_course_enrollments', to='enterprise.enterprisecustomer')), | ||
], | ||
options={ | ||
'abstract': False, | ||
}, | ||
), | ||
migrations.CreateModel( | ||
name='HistoricalDefaultEnterpriseEnrollmentRealization', | ||
fields=[ | ||
('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), | ||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), | ||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), | ||
('history_id', models.AutoField(primary_key=True, serialize=False)), | ||
('history_date', models.DateTimeField()), | ||
('history_change_reason', models.CharField(max_length=100, null=True)), | ||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), | ||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), | ||
('intended_enrollment', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='enterprise.defaultenterpriseenrollmentintention')), | ||
('realized_enrollment', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='enterprise.enterprisecourseenrollment')), | ||
], | ||
options={ | ||
'verbose_name': 'historical default enterprise enrollment realization', | ||
'verbose_name_plural': 'historical default enterprise enrollment realizations', | ||
'ordering': ('-history_date', '-history_id'), | ||
'get_latest_by': ('history_date', 'history_id'), | ||
}, | ||
bases=(simple_history.models.HistoricalChanges, models.Model), | ||
), | ||
migrations.CreateModel( | ||
name='HistoricalDefaultEnterpriseEnrollmentIntention', | ||
fields=[ | ||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), | ||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), | ||
('is_removed', models.BooleanField(default=False)), | ||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False)), | ||
('content_type', models.CharField(choices=[('course', 'Course'), ('course_run', 'Course Run')], help_text='The type of content (e.g. a course vs. a course run).', max_length=127)), | ||
('content_key', models.CharField(help_text='A course or course run that related users should be automatically enrolled into.', max_length=255)), | ||
('history_id', models.AutoField(primary_key=True, serialize=False)), | ||
('history_date', models.DateTimeField()), | ||
('history_change_reason', models.CharField(max_length=100, null=True)), | ||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), | ||
('enterprise_customer', models.ForeignKey(blank=True, db_constraint=False, help_text='The customer for which this default enrollment will be realized.', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='enterprise.enterprisecustomer')), | ||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), | ||
], | ||
options={ | ||
'verbose_name': 'historical default enterprise enrollment intention', | ||
'verbose_name_plural': 'historical default enterprise enrollment intentions', | ||
'ordering': ('-history_date', '-history_id'), | ||
'get_latest_by': ('history_date', 'history_id'), | ||
}, | ||
bases=(simple_history.models.HistoricalChanges, models.Model), | ||
), | ||
migrations.CreateModel( | ||
name='DefaultEnterpriseEnrollmentRealization', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), | ||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), | ||
('intended_enrollment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='enterprise.defaultenterpriseenrollmentintention')), | ||
('realized_enrollment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='enterprise.enterprisecourseenrollment')), | ||
], | ||
options={ | ||
'abstract': False, | ||
}, | ||
), | ||
migrations.AddField( | ||
model_name='defaultenterpriseenrollmentintention', | ||
name='realized_enrollments', | ||
field=models.ManyToManyField(through='enterprise.DefaultEnterpriseEnrollmentRealization', to='enterprise.enterprisecourseenrollment'), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters