Skip to content

Commit

Permalink
Merge pull request #64 from openedx/cag/enrollment-sink
Browse files Browse the repository at this point in the history
feat: add course enrollment sink
  • Loading branch information
Cristhian Garcia authored Jun 20, 2024
2 parents d1da857 + 3e788ff commit 8275ee6
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 2 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ Change Log
Unreleased
**********

0.10.0 - 2024-06-17
*******************

Added
=====

* A sink for the course enrollment model.

0.9.7 - 2024-06-14
******************

Expand Down
2 changes: 2 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Available Sinks
model and stores the user profile data in ClickHouse.
- `UserRetirementSink` - Listen for the `USER_RETIRE_LMS_MISC` Django signal and
remove the user PII information from ClickHouse.
- `CourseEnrollmentSink` - Listen for the `ENROLL_STATUS_CHANGE` event and stores
the course enrollment data.

Commands
========
Expand Down
2 changes: 1 addition & 1 deletion platform_plugin_aspects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
import os
from pathlib import Path

__version__ = "0.9.7"
__version__ = "0.10.0"

ROOT_DIRECTORY = Path(os.path.dirname(os.path.abspath(__file__)))
13 changes: 12 additions & 1 deletion platform_plugin_aspects/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,18 @@ class PlatformPluginAspectsConfig(AppConfig):
PluginSignals.SIGNAL_PATH: "xmodule.modulestore.django.COURSE_PUBLISHED",
}
],
}
},
"lms.djangoapp": {
# List of all plugin Signal receivers for this app and project type.
PluginSignals.RECEIVERS: [
{
# The name of the app's signal receiver function.
PluginSignals.RECEIVER_FUNC_NAME: "receive_course_enrollment_changed",
# The full path to the module where the signal is defined.
PluginSignals.SIGNAL_PATH: "common.djangoapps.student.signals.signals.ENROLL_STATUS_CHANGE",
}
],
},
},
}

Expand Down
4 changes: 4 additions & 0 deletions platform_plugin_aspects/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ def plugin_settings(settings):
"module": "openedx.core.djangoapps.external_user_ids.models",
"model": "ExternalId",
},
"course_enrollment": {
"module": "common.djangoapps.student.models",
"model": "CourseEnrollment",
},
"custom_course_edx": {
"module": "lms.djangoapps.ccx.models",
"model": "CustomCourseForEdX",
Expand Down
26 changes: 26 additions & 0 deletions platform_plugin_aspects/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.dispatch import Signal, receiver

from platform_plugin_aspects.sinks import (
CourseEnrollmentSink,
ExternalIdSink,
UserProfileSink,
UserRetirementSink,
Expand Down Expand Up @@ -34,6 +35,31 @@ def receive_course_publish( # pylint: disable=unused-argument # pragma: no cov
dump_course_to_clickhouse.delay(str(course_key))


def receive_course_enrollment_changed( # pylint: disable=unused-argument # pragma: no cover
sender, **kwargs
):
"""
Receives ENROLL_STATUS_CHANGE signal and queues the dump job.
"""
from platform_plugin_aspects.tasks import ( # pylint: disable=import-outside-toplevel
dump_data_to_clickhouse,
)

user = kwargs.get("user")
course_id = kwargs.get("course_id")

CourseEnrollment = get_model("course_enrollment")
instance = CourseEnrollment.objects.get(user=user, course_id=course_id)

sink = CourseEnrollmentSink(None, None)

dump_data_to_clickhouse.delay(
sink_module=sink.__module__,
sink_name=sink.__class__.__name__,
object_id=instance.id,
)


def on_user_profile_updated(instance):
"""
Queues the UserProfile dump job when the parent transaction is committed.
Expand Down
1 change: 1 addition & 0 deletions platform_plugin_aspects/sinks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

from .base_sink import BaseSink, ModelBaseSink
from .course_enrollment_sink import CourseEnrollmentSink
from .course_overview_sink import CourseOverviewSink, XBlockSink
from .external_id_sink import ExternalIdSink
from .user_profile_sink import UserProfileSink
Expand Down
20 changes: 20 additions & 0 deletions platform_plugin_aspects/sinks/course_enrollment_sink.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""User profile sink"""

from platform_plugin_aspects.sinks.base_sink import ModelBaseSink
from platform_plugin_aspects.sinks.serializers import CourseEnrollmentSerializer


class CourseEnrollmentSink(ModelBaseSink): # pylint: disable=abstract-method
"""
Sink for user CourseEnrollment model
"""

model = "course_enrollment"
unique_key = "id"
clickhouse_table_name = "course_enrollment"
timestamp_field = "time_last_dumped"
name = "Course Enrollment"
serializer_class = CourseEnrollmentSerializer

def get_queryset(self, start_pk=None):
return super().get_queryset(start_pk).select_related("user")
27 changes: 27 additions & 0 deletions platform_plugin_aspects/sinks/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,30 @@ def get_course_data_json(self, overview):
def get_course_key(self, overview):
"""Return the course key as a string."""
return str(overview.id)


class CourseEnrollmentSerializer(BaseSinkSerializer, serializers.ModelSerializer):
"""Serializer for the Course Enrollment model."""

course_key = serializers.SerializerMethodField()
username = serializers.CharField(source="user.username")

class Meta:
"""Meta class for the CourseEnrollmentSerializer"""

model = get_model("course_enrollment")
fields = [
"id",
"course_key",
"created",
"is_active",
"mode",
"username",
"user_id",
"dump_id",
"time_last_dumped",
]

def get_course_key(self, obj):
"""Return the course key as a string."""
return str(obj.course_id)
20 changes: 20 additions & 0 deletions platform_plugin_aspects/sinks/tests/test_course_enrollment_sink.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
Test the course enrollment sink module.
"""

from unittest.mock import patch

from platform_plugin_aspects.sinks import CourseEnrollmentSink


@patch("platform_plugin_aspects.sinks.ModelBaseSink.get_queryset")
def test_get_queryset(mock_get_queryset):
"""
Test the get_queryset method.
"""
sink = CourseEnrollmentSink(None, None)

sink.get_queryset()

mock_get_queryset.assert_called_once_with(None)
mock_get_queryset.return_value.select_related.assert_called_once_with("user")

0 comments on commit 8275ee6

Please sign in to comment.