Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/github_actions/actions/checkout-4
Browse files Browse the repository at this point in the history
  • Loading branch information
macdiesel authored Oct 29, 2024
2 parents 42fc1de + 41605ce commit addfef1
Show file tree
Hide file tree
Showing 34 changed files with 716 additions and 222 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
django-version: ["pinned"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v2
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
architecture: x64
Expand All @@ -41,7 +41,7 @@ jobs:
python-version: ["3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v2
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
architecture: x64
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/mysql8-migrations-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
uses: actions/checkout@v4

- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

Expand Down
4 changes: 4 additions & 0 deletions api-compact.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ apigateway_responses: &apigateway_responses
statusCode: "403"
404:
statusCode: "404"
423:
statusCode: "423"
429:
statusCode: "429"
500:
Expand Down Expand Up @@ -112,6 +114,8 @@ responses: &responses
description: "Forbidden"
404:
description: "Not Found"
423:
description: "Locked"
429:
description: "Too Many Requests"
500:
Expand Down
12 changes: 12 additions & 0 deletions api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ apigateway_responses:
statusCode: "403"
"404":
statusCode: "404"
"423":
statusCode: "423"
"429":
statusCode: "429"
"500":
Expand Down Expand Up @@ -92,6 +94,8 @@ responses:
description: Forbidden
"404":
description: Not Found
"423":
description: Locked
"429":
description: Too Many Requests
"500":
Expand Down Expand Up @@ -224,6 +228,8 @@ endpoints:
description: Forbidden
"404":
description: Not Found
"423":
description: Locked
"429":
description: Too Many Requests
"500":
Expand All @@ -240,6 +246,8 @@ endpoints:
statusCode: "403"
"404":
statusCode: "404"
"423":
statusCode: "423"
"429":
statusCode: "429"
"500":
Expand Down Expand Up @@ -380,6 +388,8 @@ endpoints:
description: Forbidden
"404":
description: Not Found
"423":
description: Locked
"429":
description: Too Many Requests
"500":
Expand All @@ -396,6 +406,8 @@ endpoints:
statusCode: "403"
"404":
statusCode: "404"
"423":
statusCode: "423"
"429":
statusCode: "429"
"500":
Expand Down
6 changes: 6 additions & 0 deletions license_manager/apps/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,12 @@ class Meta:
'net_days_until_expiration',
'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',
]

def get_subscription_for_auto_applied_licenses(self, obj):
Expand Down
2 changes: 1 addition & 1 deletion license_manager/apps/api/v1/tests/test_api_eventing.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def test_activate_an_assigned_license(self, _):
url = reverse('api:v1:license-activation') + '/?' + query_params.urlencode()
response = self.api_client.post(url)

assert status.HTTP_204_NO_CONTENT == response.status_code
assert status.HTTP_200_OK == response.status_code
license_to_be_activated.refresh_from_db()

assert mock_activated_track_event.call_count == 1
Expand Down
44 changes: 38 additions & 6 deletions license_manager/apps/api/v1/tests/test_license_activation_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,18 @@ def test_activate_an_assigned_license(self, mock_send_post_activation_email_task
with freeze_time(self.now):
response = self._post_request(str(self.activation_key))

assert status.HTTP_204_NO_CONTENT == response.status_code
# Verify that the response contains activated subscription license.
assert status.HTTP_200_OK == response.status_code
activated_license = response.json()
assert activated_license['uuid'] == str(license_to_be_activated.uuid)
assert activated_license['status'] == constants.ACTIVATED
expected_activation_date = self.now.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
assert activated_license['activation_date'] == expected_activation_date

# Refresh license from the database
license_to_be_activated.refresh_from_db()

# Verify that the license has been activated in the DB
assert constants.ACTIVATED == license_to_be_activated.status
assert self.lms_user_id == license_to_be_activated.lms_user_id
assert self.now == license_to_be_activated.activation_date
Expand All @@ -159,7 +169,7 @@ def test_activate_an_assigned_license(self, mock_send_post_activation_email_task
self.user.email,
)

def test_license_already_activated_returns_204(self):
def test_license_already_activated_returns_200(self):
self._assign_learner_roles(
jwt_payload_extra={
'user_id': self.lms_user_id,
Expand All @@ -174,7 +184,13 @@ def test_license_already_activated_returns_204(self):

response = self._post_request(str(self.activation_key))

assert status.HTTP_204_NO_CONTENT == response.status_code
assert status.HTTP_200_OK == response.status_code
activated_license = response.json()
assert activated_license['uuid'] == str(already_activated_license.uuid)
assert activated_license['status'] == constants.ACTIVATED
expected_activation_date = self.now.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
assert activated_license['activation_date'] == expected_activation_date

already_activated_license.refresh_from_db()
assert constants.ACTIVATED == already_activated_license.status
assert self.lms_user_id == already_activated_license.lms_user_id
Expand Down Expand Up @@ -217,7 +233,12 @@ def test_duplicate_licenses_are_cleaned_up(self, mock_license_clean, mock_email_
with freeze_time(self.now):
response = self._post_request(str(self.activation_key))

assert status.HTTP_204_NO_CONTENT == response.status_code
assert status.HTTP_200_OK == response.status_code
activated_license = response.json()
assert activated_license['uuid'] == str(license_b.uuid)
assert activated_license['status'] == constants.ACTIVATED
expected_activation_date = self.now.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
assert activated_license['activation_date'] == expected_activation_date

license_b.refresh_from_db()
assert constants.ACTIVATED == license_b.status
Expand Down Expand Up @@ -268,7 +289,12 @@ def test_activated_license_exists_with_duplicates(self, mock_license_clean, mock
with freeze_time(self.now):
response = self._post_request(str(license_a_activation_key))

assert status.HTTP_204_NO_CONTENT == response.status_code
assert status.HTTP_200_OK == response.status_code
activated_license = response.json()
assert activated_license['uuid'] == str(license_b.uuid)
assert activated_license['status'] == constants.ACTIVATED
expected_activation_date = self.now.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
assert activated_license['activation_date'] == expected_activation_date

license_b.refresh_from_db()
assert constants.ACTIVATED == license_b.status
Expand Down Expand Up @@ -341,7 +367,13 @@ def test_activating_renewed_assigned_license(self, mock_send_post_activation_ema
with freeze_time(self.now):
response = self._post_request(str(self.activation_key))

assert status.HTTP_204_NO_CONTENT == response.status_code
assert status.HTTP_200_OK == response.status_code
activated_license = response.json()
assert activated_license['uuid'] == str(current_assigned_license.uuid)
assert activated_license['status'] == constants.ACTIVATED
expected_activation_date = self.now.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
assert activated_license['activation_date'] == expected_activation_date

current_assigned_license.refresh_from_db()
prior_assigned_license.refresh_from_db()
assert prior_assigned_license.activation_date != self.now
Expand Down
7 changes: 4 additions & 3 deletions license_manager/apps/api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1779,10 +1779,10 @@ def post(self, request):
license's subscription plan.
* 404 Not Found - if the email found in the request's JWT and the provided ``activation_key``
do not match those of any existing license in an activate subscription plan.
* 204 No Content - if such a license was found, and if the license is currently ``assigned``,
* 200 OK - if such a license was found, and if the license is currently ``assigned``,
it is updated with a status of ``activated``, its ``activation_date`` is set, and its ``lms_user_id``
is updated to the value found in the request's JWT. If the license is already ``activated``,
no update is made to it.
no update is made to it. The activated license is then returned in the response.
* 422 Unprocessable Entity - if we find a license, but it's status is not currently ``assigned``
or ``activated``, we do nothing and return a 422 with a message indicating that the license
cannot be activated.
Expand All @@ -1801,7 +1801,8 @@ def post(self, request):

# There's an implied logical branch where the license is already activated
# in which case we also return as if the activation action was successful.
return Response(status=status.HTTP_204_NO_CONTENT)
serialized_license = serializers.LicenseSerializer(user_license)
return Response(serialized_license.data, status=status.HTTP_200_OK)

def _track_and_notify(self, user_license):
"""
Expand Down
8 changes: 7 additions & 1 deletion license_manager/apps/subscriptions/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,13 @@ class CustomerAgreementAdmin(admin.ModelAdmin):
'default_enterprise_catalog_uuid',
'disable_expiration_notifications',
'license_duration_before_purge',
'disable_onboarding_notifications'
'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
8 changes: 8 additions & 0 deletions license_manager/apps/subscriptions/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ class LicenseTypesToRenew:
)


class SubscriptionPlanShouldAutoApplyLicensesChoices:
CHOICES = (
(None, "----------"),
(True, "Yes"),
(False, "No")
)


class SubscriptionPlanChangeReasonChoices:
NONE = None
NEW = "new"
Expand Down
15 changes: 12 additions & 3 deletions license_manager/apps/subscriptions/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
MAX_NUM_LICENSES,
MIN_NUM_LICENSES,
SubscriptionPlanChangeReasonChoices,
SubscriptionPlanShouldAutoApplyLicensesChoices,
)
from license_manager.apps.subscriptions.models import (
CustomerAgreement,
Expand All @@ -40,6 +41,14 @@ class SubscriptionPlanForm(forms.ModelForm):
"""
Form used for the SubscriptionPlan admin class.
"""

should_auto_apply_licenses = forms.ChoiceField(
choices=SubscriptionPlanShouldAutoApplyLicensesChoices.CHOICES,
required=False,
label="Should auto apply licenses",
help_text="Whether licenses from this Subscription Plan should be auto applied."
)

# Extra form field to specify the number of licenses to be associated with the subscription plan
num_licenses = forms.IntegerField(
label="Number of Licenses",
Expand Down Expand Up @@ -276,15 +285,15 @@ def populate_subscription_for_auto_applied_licenses_choices(self, instance):
start_date__lte=now,
expiration_date__gte=now
)

current_plan = instance.auto_applicable_subscription

empty_choice = ('', '------')
choices = [empty_choice] + [(plan.uuid, plan.title) for plan in active_plans]
choice_field = forms.ChoiceField(
choices=choices,
required=False,
initial=empty_choice if not current_plan else (current_plan.uuid, current_plan.title)
initial=empty_choice if not current_plan else (current_plan.uuid, current_plan.title),
help_text="The subscription plan to be associated with auto apply licenses. Selecting a license"
" will automatically enable the \"Should auto apply licenses\" field on the subscription plan"
)
self.fields['subscription_for_auto_applied_licenses'] = choice_field

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

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('subscriptions', '0069_alter_customeragreement_disable_expiration_notifications_and_more'),
]

operations = [
migrations.AddField(
model_name='customeragreement',
name='expired_subscription_modal_messaging',
field=models.CharField(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.', max_length=512, null=True),
),
migrations.AddField(
model_name='customeragreement',
name='has_custom_license_expiration_messaging',
field=models.BooleanField(default=False, help_text='Indicates if the customer has a unique license expiration experience, instead of the standard one.'),
),
migrations.AddField(
model_name='customeragreement',
name='hyper_link_text_for_expired_modal',
field=models.CharField(blank=True, help_text='The display text for the link that will be embedded at the end of the custom expiration modal.', max_length=255, null=True),
),
migrations.AddField(
model_name='customeragreement',
name='url_for_expired_modal',
field=models.CharField(blank=True, help_text='The underlying url that will be embedded as a hyperlink at the end of the custom expiration modal.', max_length=512, null=True),
),
migrations.AddField(
model_name='historicalcustomeragreement',
name='expired_subscription_modal_messaging',
field=models.CharField(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.', max_length=512, null=True),
),
migrations.AddField(
model_name='historicalcustomeragreement',
name='has_custom_license_expiration_messaging',
field=models.BooleanField(default=False, help_text='Indicates if the customer has a unique license expiration experience, instead of the standard one.'),
),
migrations.AddField(
model_name='historicalcustomeragreement',
name='hyper_link_text_for_expired_modal',
field=models.CharField(blank=True, help_text='The display text for the link that will be embedded at the end of the custom expiration modal.', max_length=255, null=True),
),
migrations.AddField(
model_name='historicalcustomeragreement',
name='url_for_expired_modal',
field=models.CharField(blank=True, help_text='The underlying url that will be embedded as a hyperlink at the end of the custom expiration modal.', max_length=512, null=True),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.16 on 2024-10-07 15:08

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('subscriptions', '0070_customeragreement_expired_subscription_modal_messaging_and_more'),
]

operations = [
migrations.AddField(
model_name='customeragreement',
name='enable_auto_applied_subscriptions_with_universal_link',
field=models.BooleanField(default=False, help_text='By default, auto-applied subscriptions are only granted when learners join their enterprise via SSO, checking this box will enable subscription licenses to be applied when a learner joins the enterprise via Universal link as well'),
),
migrations.AddField(
model_name='historicalcustomeragreement',
name='enable_auto_applied_subscriptions_with_universal_link',
field=models.BooleanField(default=False, help_text='By default, auto-applied subscriptions are only granted when learners join their enterprise via SSO, checking this box will enable subscription licenses to be applied when a learner joins the enterprise via Universal link as well'),
),
]
Loading

0 comments on commit addfef1

Please sign in to comment.