diff --git a/openedx/features/sdaia_features/__init__.py b/openedx/features/sdaia_features/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/features/sdaia_features/course_progress/admin.py b/openedx/features/sdaia_features/course_progress/admin.py new file mode 100644 index 000000000000..ee4595b556b3 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/admin.py @@ -0,0 +1,24 @@ +""" +Admin Models +""" +""" +Django Admin page for SurveyReport. +""" + + +from django.contrib import admin +from .models import CourseCompletionEmailHistory + + +class CourseCompletionEmailHistoryAdmin(admin.ModelAdmin): + """ + Admin to manage Course Completion Email History. + """ + list_display = ( + 'id', 'user', 'course_key', 'last_progress_email_sent', + ) + search_fields = ( + 'id', 'user__username', 'user__email', 'course_key', + ) + +admin.site.register(CourseCompletionEmailHistory, CourseCompletionEmailHistoryAdmin) diff --git a/openedx/features/sdaia_features/course_progress/apps.py b/openedx/features/sdaia_features/course_progress/apps.py new file mode 100644 index 000000000000..fee8bda6b496 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/apps.py @@ -0,0 +1,20 @@ +""" +Progress Updates App Config +""" +from django.apps import AppConfig +from edx_django_utils.plugins import PluginURLs, PluginSettings +from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType + +class CourseProgressConfig(AppConfig): + name = 'openedx.features.sdaia_features.course_progress' + + plugin_app = { + PluginSettings.CONFIG: { + ProjectType.LMS: { + SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: 'settings.common'}, + } + } + } + + def ready(self): + from . import signals # pylint: disable=unused-import diff --git a/openedx/features/sdaia_features/course_progress/management/__init__.py b/openedx/features/sdaia_features/course_progress/management/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/features/sdaia_features/course_progress/management/commands/__init__.py b/openedx/features/sdaia_features/course_progress/management/commands/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/features/sdaia_features/course_progress/migrations/0001_initial.py b/openedx/features/sdaia_features/course_progress/migrations/0001_initial.py new file mode 100644 index 000000000000..f5e53ef22b1d --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.20 on 2024-02-19 07:33 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import opaque_keys.edx.django.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='CourseCompletionEmailHistory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('course_key', opaque_keys.edx.django.models.CourseKeyField(db_index=True, max_length=255)), + ('last_progress_email_sent', models.IntegerField(default=0)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/openedx/features/sdaia_features/course_progress/migrations/__init__.py b/openedx/features/sdaia_features/course_progress/migrations/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/features/sdaia_features/course_progress/models.py b/openedx/features/sdaia_features/course_progress/models.py new file mode 100644 index 000000000000..7dfbb84d9d6b --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/models.py @@ -0,0 +1,16 @@ +""" +Models +""" +from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user +from django.db import models + +from opaque_keys.edx.django.models import CourseKeyField + + +class CourseCompletionEmailHistory(models.Model): + """ + Keeps progress for a student for which he/she gets an email as he/she reaches at that particluar progress in a course. + """ + user = models.ForeignKey(User, on_delete=models.CASCADE) + course_key = CourseKeyField(max_length=255, db_index=True) + last_progress_email_sent = models.IntegerField(default=0) diff --git a/openedx/features/sdaia_features/course_progress/settings/__init__.py b/openedx/features/sdaia_features/course_progress/settings/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/features/sdaia_features/course_progress/settings/common.py b/openedx/features/sdaia_features/course_progress/settings/common.py new file mode 100644 index 000000000000..6f9ead3f8fe7 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/settings/common.py @@ -0,0 +1,8 @@ + +"""Settings""" + + +def plugin_settings(settings): + """ + Required Common settings + """ diff --git a/openedx/features/sdaia_features/course_progress/signals.py b/openedx/features/sdaia_features/course_progress/signals.py new file mode 100644 index 000000000000..b3a9bd66fa90 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/signals.py @@ -0,0 +1,70 @@ +""" +Signal handlers for the course progress emails +""" +import logging + +from completion.models import BlockCompletion +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user +from django.contrib.sites.models import Site + +from openedx.core.djangoapps.signals.signals import COURSE_GRADE_NOW_PASSED +from openedx.core.lib.celery.task_utils import emulate_http_request +from openedx.features.sdaia_features.course_progress.models import CourseCompletionEmailHistory +from openedx.features.sdaia_features.course_progress.tasks import send_user_course_progress_email, send_user_course_completion_email +from openedx.features.sdaia_features.course_progress.utils import get_user_course_progress +from xmodule.modulestore.django import modulestore + +logger = logging.getLogger(__name__) + + +@receiver(post_save, sender=BlockCompletion) +def send_course_progress_milestones_achievement_emails(**kwargs): + """ + Receives the BlockCompletion signal and sends the email to + the user if he completes a specific course progress threshold. + """ + logger.info(f"\n\n\n inside send_course_progress_milestones_achievement_emails \n\n\n") + instance = kwargs['instance'] + if not instance.context_key.is_course: + return # Content in a library or some other thing that doesn't support milestones + + course_key = instance.context_key + + course = modulestore().get_course(course_key) + course_completion_percentages_for_emails = course.course_completion_percentages_for_emails + if not course.allow_course_completion_emails or not course_completion_percentages_for_emails: + return + + course_completion_percentages_for_emails = course_completion_percentages_for_emails.split(",") + try: + course_completion_percentages_for_emails = [int(entry.strip()) for entry in course_completion_percentages_for_emails] + except Exception as e: + log.info(f"invalid course_completion_percentages_for_emails for course {str(course_key)}") + return + + user_id = instance.user_id + user = User.objects.get(id=user_id) + user_completion_progress_email_history, _ = CourseCompletionEmailHistory.objects.get_or_create(user=user, course_key=course_key) + progress_last_email_sent_at = user_completion_progress_email_history and user_completion_progress_email_history.last_progress_email_sent + if progress_last_email_sent_at == course_completion_percentages_for_emails[-1]: + return + + site = Site.objects.first() or Site.objects.get_current() + with emulate_http_request(site, user): + user_completion_percentage = get_user_course_progress(user, course_key) + + if user_completion_percentage > progress_last_email_sent_at: + for course_completion_percentages_for_email in course_completion_percentages_for_emails: + if user_completion_percentage >= course_completion_percentages_for_email > progress_last_email_sent_at: + send_user_course_progress_email.delay(user_completion_percentage, progress_last_email_sent_at, course_completion_percentages_for_email, str(course_key), user_id) + + +@receiver(COURSE_GRADE_NOW_PASSED, dispatch_uid="course_completion") +def send_course_completion_email(sender, user, course_id, **kwargs): # pylint: disable=unused-argument + """ + Listen for a signal indicating that the user has passed a course run. + """ + logger.info(f"\n\n\n inside send_course_completion_email \n\n\n") + send_user_course_completion_email.delay(user.id, str(course_id)) \ No newline at end of file diff --git a/openedx/features/sdaia_features/course_progress/tasks.py b/openedx/features/sdaia_features/course_progress/tasks.py new file mode 100644 index 000000000000..3e72cbd1ee19 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/tasks.py @@ -0,0 +1,138 @@ +""" +celery tasks for the course progress emails +""" +import logging + +from celery import shared_task +from django.conf import settings +from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user +from django.contrib.sites.models import Site +from edx_ace import ace +from edx_ace.recipient import Recipient +from opaque_keys.edx.keys import CourseKey + +from lms.djangoapps.grades.api import CourseGradeFactory +from openedx.core.djangoapps.ace_common.message import BaseMessageType +from openedx.core.djangoapps.ace_common.template_context import get_base_template_context +from openedx.core.djangoapps.content.block_structure.api import get_block_structure_manager +from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY +from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers +from openedx.core.djangoapps.user_api.preferences.api import get_user_preference +from openedx.core.lib.celery.task_utils import emulate_http_request +from openedx.features.sdaia_features.course_progress.models import CourseCompletionEmailHistory +from xmodule.modulestore.django import modulestore +from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url + +logger = logging.getLogger(__name__) + + +class UserCourseProgressEmail(BaseMessageType): + """ + Message Type Class for User Course Progress Email + """ + APP_LABEL = 'course_progress' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.options['transactional'] = True + + +class UserCourseCompletionEmail(BaseMessageType): + """ + Message Type Class for User Course Completion Email + """ + APP_LABEL = 'course_progress' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.options['transactional'] = True + + +@shared_task +def send_user_course_progress_email(current_progress, progress_last_email_sent_at, course_completion_percentages_for_email, course_key, user_id): + """ + Sends User Activation Code Via Email + """ + user = User.objects.get(id=user_id) + course_id = CourseKey.from_string(course_key) + course = modulestore().get_course(course_id) + + site = Site.objects.first() or Site.objects.get_current() + message_context = get_base_template_context(site) + course_home_url = get_learning_mfe_home_url(course_key=course_key, url_fragment='home') + platform_name = configuration_helpers.get_value_for_org( + course.org, + 'PLATFORM_NAME', + settings.PLATFORM_NAME + ) + + context={ + 'current_progress': int(current_progress), + 'progress_milestone_crossed': progress_last_email_sent_at, + 'course_key': course_key, + 'platform_name': platform_name, + 'course_name': course.display_name, + 'course_home_url': course_home_url, + } + message_context.update(context) + user_language_pref = get_user_preference(user, LANGUAGE_KEY) or settings.LANGUAGE_CODE + try: + with emulate_http_request(site, user): + msg = UserCourseProgressEmail(context=message_context).personalize( + recipient=Recipient(0, user.email), + language=user_language_pref, + user_context={'full_name': user.profile.name} + ) + ace.send(msg) + logger.info('course progress email sent to user:') + user_completion_progress_email_history = CourseCompletionEmailHistory.objects.get(user=user, course_key=course_key) + user_completion_progress_email_history.last_progress_email_sent = course_completion_percentages_for_email + user_completion_progress_email_history.save() + return True + except Exception as e: # pylint: disable=broad-except + logger.exception(str(e)) + logger.exception('Could not send course progress email sent to user') + return False + + +@shared_task +def send_user_course_completion_email(user_id, course_key): + course_id = CourseKey.from_string(course_key) + user = User.objects.get(id=user_id) + collected_block_structure = get_block_structure_manager(course_id).get_collected() + course_grade = CourseGradeFactory().read(user, collected_block_structure=collected_block_structure) + passing_grade = int(course_grade.percent * 100) + + course = modulestore().get_course(course_id) + site = Site.objects.first() or Site.objects.get_current() + message_context = get_base_template_context(site) + course_progress_url = get_learning_mfe_home_url(course_key=course_key, url_fragment='progress') + platform_name = configuration_helpers.get_value_for_org( + course.org, + 'PLATFORM_NAME', + settings.PLATFORM_NAME + ) + + context={ + 'course_key': course_key, + 'platform_name': platform_name, + 'course_name': course.display_name, + 'course_progress_url': course_progress_url, + 'passing_grade': passing_grade, + } + message_context.update(context) + user_language_pref = get_user_preference(user, LANGUAGE_KEY) or settings.LANGUAGE_CODE + try: + with emulate_http_request(site, user): + msg = UserCourseCompletionEmail(context=message_context).personalize( + recipient=Recipient(0, user.email), + language=user_language_pref, + user_context={'full_name': user.profile.name} + ) + ace.send(msg) + logger.info('course completion email sent to user:') + return True + except Exception as e: # pylint: disable=broad-except + logger.exception(str(e)) + logger.exception('Could not send course completion email sent to user') + return False diff --git a/openedx/features/sdaia_features/course_progress/templates/course_progress/base.html b/openedx/features/sdaia_features/course_progress/templates/course_progress/base.html new file mode 100644 index 000000000000..09d26f38bf06 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/templates/course_progress/base.html @@ -0,0 +1,21 @@ + +{% comment %} +As the developer of this package, don't place anything here if you can help it +since this allows developers to have interoperability between your template +structure and their own. + +Example: Developer melding the 2SoD pattern to fit inside with another pattern:: + + {% extends "base.html" %} + {% load static %} + + + {% block extra_js %} + + + {% block javascript %} + + {% endblock javascript %} + + {% endblock extra_js %} +{% endcomment %} diff --git a/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/body.html b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/body.html new file mode 100644 index 000000000000..1e8b4cad2ecf --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/body.html @@ -0,0 +1,49 @@ + + +{% load i18n %} +{% load static %} +{% block content %} +

+ {% autoescape off %} + {# xss-lint: disable=django-blocktrans-missing-escape-filter #} + {% blocktrans %}Congratulations!{% endblocktrans %} + {% endautoescape %} +
+

+

+ {% autoescape off %} + {# xss-lint: disable=django-blocktrans-missing-escape-filter #} + {% blocktrans %}{{full_name}}{% endblocktrans %} + {% endautoescape %} +
+

+

+ {% autoescape off %} + {# xss-lint: disable=django-blocktrans-missing-escape-filter #} + {% blocktrans %}You have successfully completed this course! {% endblocktrans %} + {% endautoescape %} +
+

+

+ {% autoescape off %} + {# xss-lint: disable=django-blocktrans-missing-escape-filter #} + {% blocktrans %}Please click {% endblocktrans %}{% blocktrans %}here{% endblocktrans %}{% blocktrans %} to view your accreditation. {% endblocktrans %} + {% endautoescape %} +
+
+

+

+ {% autoescape off %} + {# xss-lint: disable=django-blocktrans-missing-escape-filter #} + {% blocktrans %}Thank you. {% endblocktrans %} + {% endautoescape %} +
+

+

+ {% autoescape off %} + {# xss-lint: disable=django-blocktrans-missing-escape-filter #} + {% blocktrans %}SDAIA Academy{% endblocktrans %} + {% endautoescape %} +
+

+{% endblock %} \ No newline at end of file diff --git a/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/body.txt b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/body.txt new file mode 100644 index 000000000000..7a4c71720a03 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/body.txt @@ -0,0 +1,8 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}Congratulations!{% endblocktrans %} +{% blocktrans %}{{ full_name }}{% endblocktrans %} +{% blocktrans %}You have successfully completed this course! {% endblocktrans %} +{% blocktrans %}Please click {% endblocktrans %}{% blocktrans %}here{% endblocktrans %}{% blocktrans %} to view your accreditation. {% endblocktrans %} +{% blocktrans %}Thank you. {% endblocktrans %} +{% blocktrans %}SDAIA Academy{% endblocktrans %} +{% endautoescape %} diff --git a/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/from_name.txt b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/from_name.txt new file mode 100644 index 000000000000..dcbc23c00480 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/from_name.txt @@ -0,0 +1 @@ +{{ platform_name }} diff --git a/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/head.html b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/head.html new file mode 100644 index 000000000000..463307296e06 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/head.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/subject.txt b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/subject.txt new file mode 100644 index 000000000000..f1e01f238c78 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercoursecompletionemail/email/subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}{{ platform_name }} - {{ course_name }} | Course Completion 🎉{% endblocktrans %} +{% endautoescape %} \ No newline at end of file diff --git a/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/body.html b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/body.html new file mode 100644 index 000000000000..ed68b5eb908f --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/body.html @@ -0,0 +1,40 @@ + + +{% load i18n %} +{% load static %} +{% block content %} +

+ {% autoescape off %} + {# xss-lint: disable=django-blocktrans-missing-escape-filter #} + {% if current_progress < 50 %} + {% blocktrans %}Well done!{% endblocktrans %} + {% elif current_progress >= 50 and current_progress < 75 %} + {% blocktrans %}Great job!{% endblocktrans %} + {% else %} + {% blocktrans %}Awesome!{% endblocktrans %} + {% endif %} + {% endautoescape %} +
+

+

+ {% autoescape off %} + {# xss-lint: disable=django-blocktrans-missing-escape-filter #} + {% blocktrans %}{{full_name}}{% endblocktrans %} + {% endautoescape %} +
+

+

+ {% autoescape off %} + {# xss-lint: disable=django-blocktrans-missing-escape-filter #} + {% blocktrans %}You’ve completed {% endblocktrans %}{{ current_progress }}%{% blocktrans %} of {% endblocktrans %}"{{ course_name }}". + {% endautoescape %} +
+

+

+ {% autoescape off %} + {# xss-lint: disable=django-blocktrans-missing-escape-filter #} + {% blocktrans %}Keep it up! Continue your learning journey {% endblocktrans %}{% blocktrans %} here{% endblocktrans %}. + {% endautoescape %} +
+

+{% endblock %} \ No newline at end of file diff --git a/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/body.txt b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/body.txt new file mode 100644 index 000000000000..56ae33c7635a --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/body.txt @@ -0,0 +1,12 @@ +{% load i18n %}{% autoescape off %} +{% if current_progress < 50 %} +{% blocktrans %}Well done!{% endblocktrans %} +{% elif current_progress >= 50 and current_progress < 75 %} +{% blocktrans %}Great job!{% endblocktrans %} +{% else %} +{% blocktrans %}Awesome!{% endblocktrans %} +{% endif %} +{% blocktrans %}{{ full_name }}{% endblocktrans %} +{% blocktrans %}You’ve completed {% endblocktrans %}{{ current_progress }}%{% blocktrans %} of {% endblocktrans %}"{{ course_name }}". +{% blocktrans %}Keep it up! Continue your learning journey {% endblocktrans %}{% blocktrans %} here{% endblocktrans %}. +{% endautoescape %} diff --git a/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/from_name.txt b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/from_name.txt new file mode 100644 index 000000000000..dcbc23c00480 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/from_name.txt @@ -0,0 +1 @@ +{{ platform_name }} diff --git a/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/head.html b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/head.html new file mode 100644 index 000000000000..463307296e06 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/head.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/subject.txt b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/subject.txt new file mode 100644 index 000000000000..24e2d603ec29 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/templates/course_progress/edx_ace/usercourseprogressemail/email/subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}{{ platform_name }} - {{ course_name }} | Course Progress 🚀 {% endblocktrans %} +{% endautoescape %} \ No newline at end of file diff --git a/openedx/features/sdaia_features/course_progress/utils.py b/openedx/features/sdaia_features/course_progress/utils.py new file mode 100644 index 000000000000..e5f68674a4ff --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/utils.py @@ -0,0 +1,22 @@ +""" +Utility functions for the course progress emails +""" +from lms.djangoapps.courseware.courses import get_course_blocks_completion_summary + + +def get_user_course_progress(user, course_key): + """ + Function to get the user's course completion percentage in a course. + :param user: The user object. + :param course_key: The course key (e.g., CourseKey.from_string("edX/DemoX/Demo_Course")). + :return: completion percentage. + """ + completion_summary = get_course_blocks_completion_summary(course_key, user) + + complete_count = completion_summary.get('complete_count', 0) + incomplete_count = completion_summary.get('incomplete_count', 0) + locked_count = completion_summary.get('locked_count', 0) + total_count = complete_count + incomplete_count + locked_count + + completion_percentage = (complete_count / total_count) * 100 + return completion_percentage \ No newline at end of file diff --git a/setup.py b/setup.py index 3a6670c04137..6777b03c1429 100644 --- a/setup.py +++ b/setup.py @@ -151,6 +151,7 @@ "program_enrollments = lms.djangoapps.program_enrollments.apps:ProgramEnrollmentsConfig", "courseware_api = openedx.core.djangoapps.courseware_api.apps:CoursewareAPIConfig", "course_apps = openedx.core.djangoapps.course_apps.apps:CourseAppsConfig", + "course_progress = openedx.features.sdaia_features.course_progress.apps:CourseProgressConfig", ], "cms.djangoapp": [ "announcements = openedx.features.announcements.apps:AnnouncementsConfig", diff --git a/xmodule/course_block.py b/xmodule/course_block.py index b172b0059e1a..3cf3a1af3563 100644 --- a/xmodule/course_block.py +++ b/xmodule/course_block.py @@ -316,6 +316,18 @@ def to_json(self, value): class CourseFields: # lint-amnesty, pylint: disable=missing-class-docstring + allow_course_completion_emails = Boolean( + display_name=_("Allow Course Completion emails at different percentages"), + help=_("Enter true or false. When true, students will get email when they reach specific percentages mentioned in 'course completion percentages for emails'."), + default=False, + scope=Scope.settings + ) + course_completion_percentages_for_emails = String( + display_name=_("Course Completion Percentages"), + help=_("set comma separated percentages at which instructors want to send course completion emails e.g. '60, 70'"), + default="60, 70", + scope=Scope.settings + ) lti_passports = List( display_name=_("LTI Passports"), help=_('Enter the passports for course LTI tools in the following format: "id:client_key:client_secret".'),