From 443f6ec091abd095206313508d57a20c0778ed25 Mon Sep 17 00:00:00 2001 From: Muhammad Waleed <81019006+Waleed-Mujahid@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:07:47 +0500 Subject: [PATCH 1/2] Merge pull request #23 from edly-io/waleed/init feat: add Edly client branding settings to SiteConfiguration --- credentials/apps/core/admin.py | 6 ++++ ...dly_client_branding_and_django_settings.py | 19 ++++++++++ ...nfiguration_edx_org_short_name_and_more.py | 24 +++++++++++++ credentials/apps/core/models.py | 36 +++++++++++++++++++ credentials/apps/credentials/utils.py | 4 +-- 5 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 credentials/apps/core/migrations/0024_siteconfiguration_edly_client_branding_and_django_settings.py create mode 100644 credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name_and_more.py diff --git a/credentials/apps/core/admin.py b/credentials/apps/core/admin.py index f4cd9284e..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", @@ -178,4 +179,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/migrations/0025_siteconfiguration_edx_org_short_name_and_more.py b/credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name_and_more.py new file mode 100644 index 000000000..306c17903 --- /dev/null +++ b/credentials/apps/core/migrations/0025_siteconfiguration_edx_org_short_name_and_more.py @@ -0,0 +1,24 @@ +# 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): + + 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'), + ), + 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 32e237b15..9b5d4042d 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): """ @@ -20,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"), @@ -124,6 +138,12 @@ 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, + default=dict + ) def __str__(self): return self.site.name @@ -188,6 +208,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): """ diff --git a/credentials/apps/credentials/utils.py b/credentials/apps/credentials/utils.py index 03706ef32..2a73f9a6f 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 @@ -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 ee0fc23c595c5743614f969f338c24dc25eda9dc Mon Sep 17 00:00:00 2001 From: Muhammad Waleed <81019006+Waleed-Mujahid@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:39:57 +0500 Subject: [PATCH 2/2] fix: remove course certificate filter from user program data retrieval (#25) Updated the filtering logic in the user program data retrieval function to remove the course certificate filter, as course certificates are not saved in the credentials service. This change is intended to streamline the data retrieval process. --- credentials/apps/credentials/utils.py | 1 + credentials/apps/records/utils.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/credentials/apps/credentials/utils.py b/credentials/apps/credentials/utils.py index 2a73f9a6f..c5d31122e 100644 --- a/credentials/apps/credentials/utils.py +++ b/credentials/apps/credentials/utils.py @@ -60,6 +60,7 @@ def filter_visible(qs: "QuerySet") -> "QuerySet": Filters a UserCredentials queryset by excluding credentials that aren't supposed to be visible yet. """ + # EDLYCUSTOM: we do not save course cert in credentials service, so removing this filter for now 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 diff --git a/credentials/apps/records/utils.py b/credentials/apps/records/utils.py index 628c745ad..2f3462192 100644 --- a/credentials/apps/records/utils.py +++ b/credentials/apps/records/utils.py @@ -171,7 +171,8 @@ def get_user_program_data( allowed_statuses.append(ProgramStatus.RETIRED.value) # Get a list of programs - programs = get_filtered_programs(request_site, allowed_statuses, **course_filters) + # EDLYCUSTOM: we do not save course cert in credentials service, so removing this filter for now + programs = get_filtered_programs(request_site, allowed_statuses) # Get the completed programs and a UUID set using the program_credentials program_credential_ids = [program_credential.credential_id for program_credential in program_credentials]