Skip to content

Commit

Permalink
Merge pull request #721 from openedx/eahmadjaved/ENT-9615
Browse files Browse the repository at this point in the history
feat: update some fields in customer agreemnet model
  • Loading branch information
jajjibhai008 authored Oct 21, 2024
2 parents 5f7ba4f + 11ae7f3 commit 16c5f53
Show file tree
Hide file tree
Showing 14 changed files with 250 additions and 78 deletions.
5 changes: 4 additions & 1 deletion license_manager/apps/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,11 +354,14 @@ 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',
'hyper_link_text_for_expired_modal',
'url_for_expired_modal',
'enable_auto_applied_subscriptions_with_universal_link'
]

def get_subscription_for_auto_applied_licenses(self, obj):
Expand Down
7 changes: 5 additions & 2 deletions license_manager/apps/subscriptions/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,11 +417,14 @@ class CustomerAgreementAdmin(admin.ModelAdmin):
'disable_expiration_notifications',
'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',
'hyper_link_text_for_expired_modal',
'url_for_expired_modal',
'enable_auto_applied_subscriptions_with_universal_link'
'url_for_expired_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-18 09:53

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('subscriptions', '0071_customeragreement_enable_auto_applied_subscriptions_with_universal_link_and_more'),
]

operations = [
migrations.AddField(
model_name='customeragreement',
name='button_label_in_modal',
field=models.CharField(blank=True, help_text='The text that will appear as on the button in the expiration modal', max_length=255, null=True),
),
migrations.AddField(
model_name='customeragreement',
name='modal_header_text',
field=models.CharField(blank=True, help_text='The bold text that will appear as the header in the expiration modal.', max_length=512, null=True),
),
migrations.AddField(
model_name='customeragreement',
name='url_for_button_in_modal',
field=models.CharField(blank=True, help_text='The URL that should underly the sole button in the expiration modal', max_length=512, null=True),
),
migrations.AddField(
model_name='historicalcustomeragreement',
name='button_label_in_modal',
field=models.CharField(blank=True, help_text='The text that will appear as on the button in the expiration modal', max_length=255, null=True),
),
migrations.AddField(
model_name='historicalcustomeragreement',
name='modal_header_text',
field=models.CharField(blank=True, help_text='The bold text that will appear as the header in the expiration modal.', max_length=512, null=True),
),
migrations.AddField(
model_name='historicalcustomeragreement',
name='url_for_button_in_modal',
field=models.CharField(blank=True, help_text='The URL that should underly the sole button in the expiration modal', max_length=512, null=True),
),
migrations.AlterField(
model_name='customeragreement',
name='expired_subscription_modal_messaging',
field=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),
),
migrations.AlterField(
model_name='historicalcustomeragreement',
name='expired_subscription_modal_messaging',
field=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),
),
]
116 changes: 74 additions & 42 deletions license_manager/apps/subscriptions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
track_event,
track_license_changes,
)
from license_manager.apps.subscriptions.sanitize import sanitize_html
from license_manager.apps.subscriptions.utils import (
days_until,
get_license_activation_link,
Expand Down Expand Up @@ -150,8 +151,16 @@ class CustomerAgreement(TimeStampedModel):
)
)

expired_subscription_modal_messaging = models.CharField(
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=_(
Expand All @@ -178,6 +187,24 @@ class CustomerAgreement(TimeStampedModel):
)
)

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"
)
)

enable_auto_applied_subscriptions_with_universal_link = models.BooleanField(
default=False,
help_text=_(
Expand Down Expand Up @@ -237,54 +264,59 @@ class Meta:
verbose_name_plural = _("Customer Agreements")

def clean(self):
# Check if custom messaging is enabled and messaging field is blank
"""
Custom clean method to validate fields based on the 'Has Custom License Expiration Messaging' flag.
"""
errors = {}

# Sanitize the expired_subscription_modal_messaging field
if self.expired_subscription_modal_messaging:
self.expired_subscription_modal_messaging = sanitize_html(self.expired_subscription_modal_messaging)

error_message = "This field cannot be blank if 'Has Custom License Expiration Messaging' is checked."
# Validate fields when custom messaging is enabled
if self.has_custom_license_expiration_messaging:
if not self.expired_subscription_modal_messaging:
raise ValidationError({
"expired_subscription_modal_messaging": (
"This field cannot be blank if 'Has Custom License Expiration Messaging' is checked."
)
})

# Validate that URL field is not blank if hyperlink text is provided
if self.hyper_link_text_for_expired_modal and not self.url_for_expired_modal:
raise ValidationError({
"url_for_expired_modal": (
"This field cannot be blank if 'Hyper Link Text for Expired Modal' has values."
)
})
required_fields = {
"modal_header_text": error_message,
"expired_subscription_modal_messaging": error_message,
"button_label_in_modal": error_message,
"url_for_button_in_modal": error_message,
"hyper_link_text_for_expired_modal": error_message,
"url_for_expired_modal": error_message
}

# Validate that hyperlink text is not blank if URL is provided
if self.url_for_expired_modal and not self.hyper_link_text_for_expired_modal:
raise ValidationError({
"hyper_link_text_for_expired_modal": (
"This field cannot be blank if 'URL for Expired Modal' has values."
)
})
# Check if any required fields are missing
for field, error_message in required_fields.items():
if not getattr(self, field):
errors[field] = error_message

# Ensure all fields are blank if custom messaging is disabled
if not self.has_custom_license_expiration_messaging:
if any([
self.expired_subscription_modal_messaging,
self.hyper_link_text_for_expired_modal,
self.url_for_expired_modal
]):
fields_to_check = [
"modal_header_text",
"expired_subscription_modal_messaging",
"button_label_in_modal",
"url_for_button_in_modal",
"hyper_link_text_for_expired_modal",
"url_for_expired_modal",
]
if any(getattr(self, field) for field in fields_to_check):
error_msg = "This field must be blank if 'Has Custom License Expiration Messaging' is unchecked."
raise ValidationError({
"expired_subscription_modal_messaging": error_msg,
"hyper_link_text_for_expired_modal": error_msg,
"url_for_expired_modal": error_msg,
})

def __str__(self):
"""
Return human-readable string representation.
"""
return (
"<CustomerAgreement: '{}'>".format(
self.enterprise_customer_slug or self.enterprise_customer_name
)
errors = {field: error_msg for field in fields_to_check}

# Raise ValidationError if there are any errors
if errors:
raise ValidationError(errors)

def __str__(self):
"""
Return human-readable string representation.
"""
return (
"<CustomerAgreement: '{}'>".format(
self.enterprise_customer_slug or self.enterprise_customer_name
)
)


class PlanType(models.Model):
Expand Down
30 changes: 30 additions & 0 deletions license_manager/apps/subscriptions/sanitize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import bleach


def sanitize_html(html_content):
"""
Sanitize HTML content to allow only safe tags and attributes,
while disallowing JavaScript and unsafe protocols.
"""
# Define allowed tags and attributes
allowed_tags = bleach.ALLOWED_TAGS # Allow all standard HTML tags
allowed_attrs = {"*": ["className", "class", "style", "id"]}

# Clean the HTML content
sanitized_content = bleach.clean(
html_content,
tags=allowed_tags,
attributes=allowed_attrs,
strip=True, # Strip disallowed tags completely
protocols=["http", "https"], # Only allow http and https URLs
)

# Use bleach.linkify to ensure no javascript: links in <a> tags
sanitized_content = bleach.linkify(
sanitized_content,
callbacks=[
bleach.callbacks.nofollow
], # Apply 'nofollow' to external links for safety
)

return sanitized_content
1 change: 1 addition & 0 deletions requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ rules
simplejson
zipp
django-log-request-id
bleach
13 changes: 9 additions & 4 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ backoff==1.10.0
# analytics-python
billiard==4.2.1
# via celery
boto3==1.35.39
bleach==6.1.0
# via -r requirements/base.in
boto3==1.35.42
# via django-ses
botocore==1.35.39
botocore==1.35.42
# via
# boto3
# s3transfer
Expand Down Expand Up @@ -153,7 +155,7 @@ edx-braze-client==0.2.5
# via -r requirements/base.in
edx-celeryutils==1.3.0
# via -r requirements/base.in
edx-django-utils==6.0.0
edx-django-utils==6.1.0
# via
# -r requirements/base.in
# edx-drf-extensions
Expand Down Expand Up @@ -195,7 +197,7 @@ monotonic==1.6
# via analytics-python
mysqlclient==2.2.4
# via -r requirements/base.in
newrelic==10.1.0
newrelic==10.2.0
# via edx-django-utils
oauthlib==3.2.2
# via
Expand Down Expand Up @@ -267,6 +269,7 @@ simplejson==3.19.3
six==1.16.0
# via
# analytics-python
# bleach
# edx-auth-backends
# edx-rbac
# python-dateutil
Expand Down Expand Up @@ -304,6 +307,8 @@ vine==5.1.0
# kombu
wcwidth==0.2.13
# via prompt-toolkit
webencodings==0.5.1
# via bleach
zipp==3.20.2
# via -r requirements/base.in

Expand Down
17 changes: 12 additions & 5 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ billiard==4.2.1
# via
# -r requirements/validation.txt
# celery
boto3==1.35.39
bleach==6.1.0
# via -r requirements/validation.txt
boto3==1.35.42
# via
# -r requirements/validation.txt
# django-ses
botocore==1.35.39
botocore==1.35.42
# via
# -r requirements/validation.txt
# boto3
Expand Down Expand Up @@ -225,7 +227,7 @@ edx-braze-client==0.2.5
# via -r requirements/validation.txt
edx-celeryutils==1.3.0
# via -r requirements/validation.txt
edx-django-utils==6.0.0
edx-django-utils==6.1.0
# via
# -r requirements/validation.txt
# edx-drf-extensions
Expand All @@ -251,7 +253,7 @@ edx-toggles==5.2.0
# via -r requirements/validation.txt
factory-boy==3.3.1
# via -r requirements/validation.txt
faker==30.3.0
faker==30.6.0
# via
# -r requirements/validation.txt
# factory-boy
Expand Down Expand Up @@ -333,7 +335,7 @@ more-itertools==10.5.0
# via inflect
mysqlclient==2.2.4
# via -r requirements/validation.txt
newrelic==10.1.0
newrelic==10.2.0
# via
# -r requirements/validation.txt
# edx-django-utils
Expand Down Expand Up @@ -513,6 +515,7 @@ six==1.16.0
# via
# -r requirements/validation.txt
# analytics-python
# bleach
# edx-auth-backends
# edx-lint
# edx-rbac
Expand Down Expand Up @@ -582,6 +585,10 @@ wcwidth==0.2.13
# via
# -r requirements/validation.txt
# prompt-toolkit
webencodings==0.5.1
# via
# -r requirements/validation.txt
# bleach
wheel==0.44.0
# via
# -r requirements/pip-tools.txt
Expand Down
Loading

0 comments on commit 16c5f53

Please sign in to comment.