Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add custom license expiration v2 fields. #734

Merged
merged 6 commits into from
Oct 31, 2024
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
2 changes: 2 additions & 0 deletions .annotation_safe_list.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ waffle.Switch:
".. no_pii:": "This model has no PII"
subscriptions.HistoricalSubscriptionLicenseSource:
".. no_pii:": "This model has no PII"
subscriptions.HistoricalCustomSubscriptionExpirationMessaging:
".. no_pii:": "This model has no PII"
45 changes: 40 additions & 5 deletions license_manager/apps/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,11 @@ class MinimalCustomerAgreementSerializer(serializers.ModelSerializer):
"""

subscription_for_auto_applied_licenses = serializers.SerializerMethodField()
has_custom_license_expiration_messaging_v2 = serializers.SerializerMethodField()
modal_header_text_v2 = serializers.SerializerMethodField()
expired_subscription_modal_messaging_v2 = serializers.SerializerMethodField()
button_label_in_modal_v2 = serializers.SerializerMethodField()
url_for_button_in_modal_v2 = serializers.SerializerMethodField()

class Meta:
model = CustomerAgreement
Expand All @@ -355,17 +360,47 @@ class Meta:
'subscription_for_auto_applied_licenses',
'available_subscription_catalogs',
'enable_auto_applied_subscriptions_with_universal_link',
'has_custom_license_expiration_messaging',
'modal_header_text',
'expired_subscription_modal_messaging',
'button_label_in_modal',
'url_for_button_in_modal',
'has_custom_license_expiration_messaging_v2',
'modal_header_text_v2',
'expired_subscription_modal_messaging_v2',
'button_label_in_modal_v2',
'url_for_button_in_modal_v2',
]

def get_subscription_for_auto_applied_licenses(self, obj):
subscription_plan = obj.auto_applicable_subscription
return subscription_plan.uuid if subscription_plan else None

def get_has_custom_license_expiration_messaging_v2(self, obj):
custom_subscription_expiration_messaging = obj.custom_subscription_expiration_messaging
if custom_subscription_expiration_messaging:
return custom_subscription_expiration_messaging.has_custom_license_expiration_messaging
return None

def get_modal_header_text_v2(self, obj):
custom_subscription_expiration_messaging = obj.custom_subscription_expiration_messaging
if custom_subscription_expiration_messaging:
return custom_subscription_expiration_messaging.modal_header_text
return None

def get_expired_subscription_modal_messaging_v2(self, obj):
custom_subscription_expiration_messaging = obj.custom_subscription_expiration_messaging
if custom_subscription_expiration_messaging:
return custom_subscription_expiration_messaging.expired_subscription_modal_messaging
return None

def get_button_label_in_modal_v2(self, obj):
custom_subscription_expiration_messaging = obj.custom_subscription_expiration_messaging
if custom_subscription_expiration_messaging:
return custom_subscription_expiration_messaging.button_label_in_modal
return None

def get_url_for_button_in_modal_v2(self, obj):
custom_subscription_expiration_messaging = obj.custom_subscription_expiration_messaging
if custom_subscription_expiration_messaging:
return custom_subscription_expiration_messaging.url_for_button_in_modal
return None


class CustomerAgreementSerializer(MinimalCustomerAgreementSerializer):
"""
Expand Down
14 changes: 9 additions & 5 deletions license_manager/apps/subscriptions/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
)
from license_manager.apps.subscriptions.models import (
CustomerAgreement,
CustomSubscriptionExpirationMessaging,
License,
LicenseEvent,
LicenseTransferJob,
Expand Down Expand Up @@ -402,6 +403,14 @@ def save_model(self, request, obj, form, change):
obj.provision_licenses()


@admin.register(CustomSubscriptionExpirationMessaging)
class CustomSubscriptionExpirationMessagingAdmin(DjangoQLSearchMixin, admin.ModelAdmin):
list_display = (
'customer_agreement',
'has_custom_license_expiration_messaging',
)


@admin.register(CustomerAgreement)
class CustomerAgreementAdmin(admin.ModelAdmin):
form = CustomerAgreementAdminForm
Expand All @@ -418,11 +427,6 @@ class CustomerAgreementAdmin(admin.ModelAdmin):
'license_duration_before_purge',
'disable_onboarding_notifications',
'enable_auto_applied_subscriptions_with_universal_link',
'has_custom_license_expiration_messaging',
'modal_header_text',
'expired_subscription_modal_messaging',
'button_label_in_modal',
'url_for_button_in_modal',
)
custom_fields = ('subscription_for_auto_applied_licenses',)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Generated by Django 4.2.16 on 2024-10-31 13:59

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import simple_history.models


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('subscriptions', '0073_remove_customeragreement_hyper_link_text_for_expired_modal_and_more'),
]

operations = [
migrations.CreateModel(
name='HistoricalCustomSubscriptionExpirationMessaging',
fields=[
('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('has_custom_license_expiration_messaging', models.BooleanField(default=False, help_text='Indicates if the customer has a unique license expiration experience, instead of the standard one.')),
('modal_header_text', models.CharField(blank=True, help_text='The bold text that will appear as the header in the expiration modal.', max_length=512, null=True)),
('expired_subscription_modal_messaging', models.TextField(blank=True, help_text='The content of a modal that will appear to learners upon subscription expiration. This text can be used for custom guidance per customer.', null=True)),
('button_label_in_modal', models.CharField(blank=True, help_text='The text that will appear as on the button in the expiration modal', max_length=255, null=True)),
('url_for_button_in_modal', models.CharField(blank=True, help_text='The URL that should underly the sole button in the expiration modal', max_length=512, null=True)),
('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)),
('customer_agreement', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='subscriptions.customeragreement')),
('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 custom subscription expiration messaging',
'verbose_name_plural': 'historical custom subscription expiration messagings',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='CustomSubscriptionExpirationMessaging',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('has_custom_license_expiration_messaging', models.BooleanField(default=False, help_text='Indicates if the customer has a unique license expiration experience, instead of the standard one.')),
('modal_header_text', models.CharField(blank=True, help_text='The bold text that will appear as the header in the expiration modal.', max_length=512, null=True)),
('expired_subscription_modal_messaging', models.TextField(blank=True, help_text='The content of a modal that will appear to learners upon subscription expiration. This text can be used for custom guidance per customer.', null=True)),
('button_label_in_modal', models.CharField(blank=True, help_text='The text that will appear as on the button in the expiration modal', max_length=255, null=True)),
('url_for_button_in_modal', models.CharField(blank=True, help_text='The URL that should underly the sole button in the expiration modal', max_length=512, null=True)),
('customer_agreement', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='_custom_subscription_expiration_messaging', to='subscriptions.customeragreement')),
],
),
]
70 changes: 70 additions & 0 deletions license_manager/apps/subscriptions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,16 @@ def auto_applicable_subscription(self):

return plan

@property
def custom_subscription_expiration_messaging(self):
"""
Returns the custom subscription expiration messaging associated with this customer agreement.
"""
try:
return self._custom_subscription_expiration_messaging
except CustomSubscriptionExpirationMessaging.DoesNotExist:
return None

class Meta:
verbose_name = _("Customer Agreement")
verbose_name_plural = _("Customer Agreements")
Expand Down Expand Up @@ -303,6 +313,66 @@ def __str__(self):
)


class CustomSubscriptionExpirationMessaging(models.Model):
"""
Custom subscription expiration messaging

.. no_pii: This model has no PII
"""

customer_agreement = models.OneToOneField(
CustomerAgreement,
on_delete=models.CASCADE,
related_name='_custom_subscription_expiration_messaging',
unique=True,
)

has_custom_license_expiration_messaging = models.BooleanField(
default=False,
help_text=_(
"Indicates if the customer has a unique license expiration experience, instead of the standard one."
)
)

modal_header_text = models.CharField(
max_length=512,
blank=True,
null=True,
help_text=_(
"The bold text that will appear as the header in the expiration modal."
)
)

expired_subscription_modal_messaging = models.TextField(
blank=True,
null=True,
help_text=_(
"The content of a modal that will appear to learners upon subscription expiration. This text can be used "
"for custom guidance per customer."
)
)

button_label_in_modal = models.CharField(
max_length=255,
blank=True,
null=True,
help_text=_(
"The text that will appear as on the button in the expiration modal"
)
)

url_for_button_in_modal = models.CharField(
max_length=512,
blank=True,
null=True,
help_text=_(
"The URL that should underly the sole button in the expiration modal"
)
)

history = HistoricalRecords()


class PlanType(models.Model):
"""
Stores top-level information related to available enterprise Subscription plan types.
Expand Down
4 changes: 2 additions & 2 deletions pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
# SERIOUSLY.
#
# ------------------------------
# Generated by edx-lint version: 5.4.0
# Generated by edx-lint version: 5.4.1
# ------------------------------
[MASTER]
ignore = ,migrations, settings, wsgi.py
Expand Down Expand Up @@ -401,4 +401,4 @@ int-import-graph =
[EXCEPTIONS]
overgeneral-exceptions = builtins.Exception

# ea586deca5871e992466c748232382f9dfadff18
# 3efa0bd414ae95120c9d8ac2ed13b2b5e1ed1e69