From 59147915448e5e89d0e33d27a64f9fadecad3a35 Mon Sep 17 00:00:00 2001 From: "waleed.mujahid" Date: Tue, 16 Sep 2025 17:00:06 +0500 Subject: [PATCH 1/5] feat: add Edly client branding settings to SiteConfiguration - Introduced a new JSONField for 'edly_client_branding_and_django_settings' in the SiteConfiguration model. - Updated the SiteConfigurationAdmin to include the new field in the admin interface. - Added a method to retrieve configuration values from the new field with error logging. This enhancement allows for better customization of the Edly client theme settings. --- credentials/apps/core/admin.py | 5 ++++ ...dly_client_branding_and_django_settings.py | 19 ++++++++++++++ credentials/apps/core/models.py | 26 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 credentials/apps/core/migrations/0024_siteconfiguration_edly_client_branding_and_django_settings.py diff --git a/credentials/apps/core/admin.py b/credentials/apps/core/admin.py index f4cd9284e..245ada9d6 100644 --- a/credentials/apps/core/admin.py +++ b/credentials/apps/core/admin.py @@ -178,4 +178,9 @@ class SiteConfigurationAdmin(admin.ModelAdmin): ) }, ), + (_('Edly Settings'), { + 'fields': ( + 'edly_client_branding_and_django_settings', + ) + }), ) diff --git a/credentials/apps/core/migrations/0024_siteconfiguration_edly_client_branding_and_django_settings.py b/credentials/apps/core/migrations/0024_siteconfiguration_edly_client_branding_and_django_settings.py new file mode 100644 index 000000000..2f963b90e --- /dev/null +++ b/credentials/apps/core/migrations/0024_siteconfiguration_edly_client_branding_and_django_settings.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.20 on 2025-09-16 11:47 + +from django.db import migrations +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0023_add_core_user_indices'), + ] + + operations = [ + migrations.AddField( + model_name='siteconfiguration', + name='edly_client_branding_and_django_settings', + field=jsonfield.fields.JSONField(default={}, help_text='JSON string containing edly client theme branding & Django settings.', verbose_name='Edly client theme branding & Django settings'), + ), + ] diff --git a/credentials/apps/core/models.py b/credentials/apps/core/models.py index 32e237b15..8fbb7b832 100644 --- a/credentials/apps/core/models.py +++ b/credentials/apps/core/models.py @@ -2,6 +2,7 @@ import hashlib from urllib.parse import urljoin +import logging from django.conf import settings from django.contrib.auth.models import AbstractUser @@ -10,7 +11,9 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from edx_rest_api_client.client import OAuthAPIClient +from jsonfield.fields import JSONField +log = logging.getLogger(__name__) class SiteConfiguration(models.Model): """ @@ -124,6 +127,13 @@ class SiteConfiguration(models.Model): enable_twitter_sharing = models.BooleanField( verbose_name=_("Enable Twitter sharing"), help_text=_("Enable sharing via Twitter"), default=True ) + edly_client_branding_and_django_settings = JSONField( + verbose_name=_('Edly client theme branding & Django settings'), + help_text=_('JSON string containing edly client theme branding & Django settings.'), + null=False, + blank=False, + default={} + ) def __str__(self): return self.site.name @@ -188,6 +198,22 @@ def get_user_api_data(self, username): return user_data + def get_edly_configuration_value(self, name, default=None): + """ + Return Configuration value for the key specified as name argument. + Function logs a message if there is an error retrieving a key. + Arguments: + name (str): Name of the key for which to return configuration value. + default: default value to return if key is not found in the configuration + Returns: + Configuration value for the given key or returns `None` if default is not available. + """ + try: + return self.edly_client_branding_and_django_settings.get(name, default) + except AttributeError as error: + log.exception('Invalid JSON data. \n [%s]', error) + + return default class User(AbstractUser): """ From 15130fa8b603fbd6d2fa7cadf0f1477299e571c6 Mon Sep 17 00:00:00 2001 From: "waleed.mujahid" Date: Wed, 17 Sep 2025 11:57:12 +0500 Subject: [PATCH 2/5] feat: add edx_org_short_name field to SiteConfiguration model - Introduced a new CharField 'edx_org_short_name' to the SiteConfiguration model with unique constraints and help text for better organization identification. - Updated SiteConfigurationAdmin to include the new field in the admin interface. - Added a migration to create the new field in the database. This enhancement improves the ability to uniquely identify organizations within the system. --- credentials/apps/core/admin.py | 1 + ...025_siteconfiguration_edx_org_short_name.py | 18 ++++++++++++++++++ credentials/apps/core/models.py | 11 +++++++++++ 3 files changed, 30 insertions(+) create mode 100644 credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name.py diff --git a/credentials/apps/core/admin.py b/credentials/apps/core/admin.py index 245ada9d6..3400ebecb 100644 --- a/credentials/apps/core/admin.py +++ b/credentials/apps/core/admin.py @@ -142,6 +142,7 @@ class SiteConfigurationAdmin(admin.ModelAdmin): { "fields": ( "site", + "edx_org_short_name", "platform_name", "company_name", "segment_key", diff --git a/credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name.py b/credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name.py new file mode 100644 index 000000000..186e70bd0 --- /dev/null +++ b/credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.20 on 2025-09-17 06:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0024_siteconfiguration_edly_client_branding_and_django_settings'), + ] + + operations = [ + migrations.AddField( + model_name='siteconfiguration', + name='edx_org_short_name', + field=models.CharField(help_text='Unique, short string identifier for organization, same as LMS. Please do not use spaces or special characters. Only allowed special characters are period (.), hyphen (-) and underscore (_).', max_length=255, null=True, unique=True, verbose_name='Organization short name'), + ), + ] diff --git a/credentials/apps/core/models.py b/credentials/apps/core/models.py index 8fbb7b832..805d7a35c 100644 --- a/credentials/apps/core/models.py +++ b/credentials/apps/core/models.py @@ -23,6 +23,17 @@ class SiteConfiguration(models.Model): """ site = models.OneToOneField(Site, null=False, blank=False, on_delete=models.CASCADE) + edx_org_short_name = models.CharField( + max_length=255, + unique=True, + verbose_name=u'Organization short name', + help_text=_( + 'Unique, short string identifier for organization, same as LMS. ' + 'Please do not use spaces or special characters. ' + 'Only allowed special characters are period (.), hyphen (-) and underscore (_).' + ), + null=True, + ) platform_name = models.CharField( verbose_name=_("Platform Name"), help_text=_("Name of your Open edX platform"), From ef0f68abe4186a11d9db97ea2b51f46ffc2377ad Mon Sep 17 00:00:00 2001 From: "waleed.mujahid" Date: Wed, 17 Sep 2025 16:37:45 +0500 Subject: [PATCH 3/5] fix: correct filtering logic in filter_visible function - Updated the filter_visible function to apply the _filter_visible_course_certificates directly to the queryset instead of filtering for non-null course credentials first. - we do not generate course cert against a program on credentials --- credentials/apps/credentials/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credentials/apps/credentials/utils.py b/credentials/apps/credentials/utils.py index 03706ef32..5bd5a7fc4 100644 --- a/credentials/apps/credentials/utils.py +++ b/credentials/apps/credentials/utils.py @@ -60,7 +60,7 @@ def filter_visible(qs: "QuerySet") -> "QuerySet": Filters a UserCredentials queryset by excluding credentials that aren't supposed to be visible yet. """ - visible_course_certs = _filter_visible_course_certificates(qs.filter(course_credentials__isnull=False)) + visible_course_certs = _filter_visible_course_certificates(qs) visible_program_certs = _filter_visible_program_certificates(qs.filter(program_credentials__isnull=False)) visible_certs = visible_course_certs | visible_program_certs From 1ef80254603dcd5c4ef7cc32962e6bf6245a8acc Mon Sep 17 00:00:00 2001 From: "waleed.mujahid" Date: Wed, 17 Sep 2025 17:20:48 +0500 Subject: [PATCH 4/5] feat: fix program certificate visibilty issue credentials check if the certifciates are earned for course runs for a program however we do not want this. In our case program cert is only generated when user completes all courses on lms --- credentials/apps/credentials/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credentials/apps/credentials/utils.py b/credentials/apps/credentials/utils.py index 5bd5a7fc4..2a73f9a6f 100644 --- a/credentials/apps/credentials/utils.py +++ b/credentials/apps/credentials/utils.py @@ -121,7 +121,7 @@ def _get_program_certificate_visible_date(user_program_credential: UserCredentia (DateTime or None): The date on which the program credential should be visible. (It shouldn’t return None but is technically possible.) """ - last_date = None # type: Optional[datetime.datetime] + last_date = user_program_credential.created for course_run in user_program_credential.credential.program.course_runs.all(): # Does the user have a course cert for this course run? course_run_cert = UserCredential.objects.filter( From 63f536f988d129791954d744d98c3459cd39e879 Mon Sep 17 00:00:00 2001 From: "waleed.mujahid" Date: Wed, 8 Oct 2025 10:48:54 +0500 Subject: [PATCH 5/5] feat: update default value for edly_client_branding_and_django_settings field - Changed the default value of the 'edly_client_branding_and_django_settings' JSONField in the SiteConfiguration model from an empty dictionary to the dict constructor for better clarity. - Added a migration to reflect this change in the database schema. This update enhances the model's configuration handling. --- ...0025_siteconfiguration_edx_org_short_name_and_more.py} | 8 +++++++- credentials/apps/core/models.py | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) rename credentials/apps/core/migrations/{0025_siteconfiguration_edx_org_short_name.py => 0025_siteconfiguration_edx_org_short_name_and_more.py} (61%) diff --git a/credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name.py b/credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name_and_more.py similarity index 61% rename from credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name.py rename to credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name_and_more.py index 186e70bd0..306c17903 100644 --- a/credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name.py +++ b/credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name_and_more.py @@ -1,6 +1,7 @@ -# Generated by Django 4.2.20 on 2025-09-17 06:18 +# Generated by Django 4.2.20 on 2025-10-08 06:04 from django.db import migrations, models +import jsonfield.fields class Migration(migrations.Migration): @@ -15,4 +16,9 @@ class Migration(migrations.Migration): name='edx_org_short_name', field=models.CharField(help_text='Unique, short string identifier for organization, same as LMS. Please do not use spaces or special characters. Only allowed special characters are period (.), hyphen (-) and underscore (_).', max_length=255, null=True, unique=True, verbose_name='Organization short name'), ), + migrations.AlterField( + model_name='siteconfiguration', + name='edly_client_branding_and_django_settings', + field=jsonfield.fields.JSONField(default=dict, help_text='JSON string containing edly client theme branding & Django settings.', verbose_name='Edly client theme branding & Django settings'), + ), ] diff --git a/credentials/apps/core/models.py b/credentials/apps/core/models.py index 805d7a35c..9b5d4042d 100644 --- a/credentials/apps/core/models.py +++ b/credentials/apps/core/models.py @@ -142,8 +142,7 @@ class SiteConfiguration(models.Model): verbose_name=_('Edly client theme branding & Django settings'), help_text=_('JSON string containing edly client theme branding & Django settings.'), null=False, - blank=False, - default={} + default=dict ) def __str__(self):