Learn how to add custom code to the login sequence of your Open edX installation by leveraging Django Dispatch Signals.
Suppose that you need something special to happen in Open edX every time a user is authenticated? for example, suppose that you want to run an A/B split test, or that you need to send an email message to a small select group of active users like say, only the users who are currently enrolled in the paid versions of your courses. There are a few ways to accomplish this, but one method in particular really stands out:
Django Signals Dispatcher
Django includes a “signal dispatcher” which helps allow decoupled applications get notified when actions occur elsewhere in the framework. In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place. They’re especially useful when many pieces of code may be interested in the same events.
Django provides a set of built-in signals that let user code get notified by Django itself of certain actions such as for example, when users login or logout.
Django Dispatch Signals is preferable for a couple of reasons. First, Dispatch Signals enable you to avoid, or at least minimize, modifications to existing source code in the edx-platform repository. This not only greatly reduces the probability of introducing buggy behavior, but it also simplifies future upgrades of the software. Additionally, it is also a more fool-proof approach if your intention is to add custom functionality to ALL user authentications regardless of how users authenticated.
Importantly, Django dispatches a signal each time a user is authenticated, regardless of the authentication method. Using Django Signal Dispatch you can listen for and add your custom code to the login sequence of all user authentications even if your Open edX platform offers users multiple ways to authenticate (username & password, LTI, oAuth, etc.) Your code will even execute if you bypass Open edX LMS/CMS applications entirely and login directly to the Django admin console.
In this tutorial we will scaffold a small custom Django app in a fork of the edx-platform repository. You can reference the exact source code here for the complete Django app.
A Practical Example
The following code is the gist of how to get Django Dispatch Signals working in your Open edX project. Keep in mind that this is a simplification of the actual code that you would use for your project, so definitely use the real source code in the github repository rather than copying & pasting these snippets.
Only two new Python modules are required, as presented below.
1.) Register your new Django app
The first four lines of the module that follows will register a new Django app named common.djangoapps.custom_login. This is all that is required to create a completely new app within Open edX. Just imagine: limitless possibilities!!!The bottom two lines are less intuitive: ready() is a method this is part of Django’s AppConfig class and which fires immediately after the app has been registered with the Django framework at project startup. We leverage the ready() method to import our custom Python module receivers.py only after everything else is Django has loaded, thus averting a hazardous race situation. Perhaps equally unintuitive is why we need to import receivers.py in the first place since it clearly is not used in our implementation of ready(). We import receivers.py here because this is how we activate the Django receiver decorators in the module. That is, the code line,
from django.apps import AppConfig class CustomLoginConfig(AppConfig): name = 'common.djangoapps.custom_login' verbose_name = 'Sample Custom Login Helpers' def ready(self): from .signals import receivers
2.) Create your custom Python functions
This next Python module is pretty straightforward, but I should point out that you should try to contain all of your Django Dispatch Receivers in a single Python module if at all possible. Below, our custom Python function, my_custom_login_stuff(), gets executed any time the Python decorator @receiver() receives a “user_logged_in” signal. Our one-line function doesn’t do anything interesting, but you can review the actual source code for receivers.py in the Github repository to see a more real-world example how this could be put to work in your Open edX installation.
from django.dispatch import receiver from django.contrib.auth.signals import user_logged_in @receiver(user_logged_in) def my_custom_login_stuff(sender, request, user, **kwargs): print('my_custom_login_stuff() was called.')
3.) Add your new Django app to INSTALLED_APPS
If you are unfamiliar with the concept of INSTALLED_APPS then you can read more about it in the official Django documentation on adding Django applications to a Django project. The salient points for purposes of this tutorial are that it is important to add your new Django app to the BOTTOM of common.py, and that you should use INSTALLED_APPS.append() rather than any other alternative which you might otherwise have preferred.
# add this to the BOTTOM of common.py INSTALLED_APPS.append('common.djangoapps.custom_login.apps.CustomLoginConfig')
4.) Restart your Open edX platform
Modifications to any of the Python configuration files located in /edx/app/edxapp/edx-platform/lms/envs are only read at project startup, which happens either when you reboot the server or use the command line
/edx/bin/supervisorctl restart lms to restart the LMS application.
There Are Many Custom Open edX Dispatch Signals
Even though this particular article is about customizing authentication behavior, it turns out that Open edX extends Django’s built-in Dispatch functionality with many other useful Dispatch Signals that you can leverage for a variety of use cases. My code sample, in addition to enhancing the authentication behavior for example, also demonstrates how to use the custom ENROLLMENT_TRACK_UPDATED signal to add custom functionality when a user upgrades from an Audited to a Paid enrollment track using the e-commerce module.
Following are a few more of Open edX’s custom Dispatch Signals.
|User Management Signals||Description|
|USER_FIELD_CHANGED||Used to signal a field value change in the user (aka “student”) table|
|LEARNER_NOW_VERIFIED||Signal that indicates that a user has become verified for certificate purposes|
|USER_ACCOUNT_ACTIVATED||Signal indicating email verification was successfully completed|
|REGISTER_USER||Signal indicating that a new user was successfully created.|
|Enrollment and Course Progress||Description|
|ENROLLMENT_TRACK_UPDATED||Signal that indicates that a student has modified their enrollment mode. This is most commonly fired via e-commerce events when a student pays for a Verified Certificate for example.|
|SCORE_PUBLISHED||Signal that indicates that a student just submitted a response to a graded assignment problem including for example, homework, quiz, mid-term and final exam assignment types.|
|COURSE_CERT_AWARDED||Signal that indicates that a student was just awarded a course completion certificate|
|COURSE_GRADE_NOW_PASSED||Signal that indicates that a student just achieved the minimum passing grade for a course.|
|COURSE_GRADE_NOW_FAILED||Signal that indicates that a student’s grade status just changed from “passing” to “not passing”.|
|Email List Management||Description|
|USER_RETIRE_MAILINGS||Signal to retire a user from LMS-initiated mailings (course mailings, etc)|
|USER_RETIRE_LMS_CRITICAL||Signal to retire LMS critical information|
|USER_RETIRE_LMS_MISC||Signal to retire LMS misc information|
Why is your code added to “common”?
Common is a Django project that is fully contained within the edx-platform repository. It contains a set of Django apps that are shared between Course Management Studio (cms) and the Learning Management System (lms). Note that the edx-platform repository contains four distinct Django projects: cms, common, lms, and openedx. Strictly technically speaking, you could place your custom Django app in any of these four. So, why did we choose “common”?
At the risk of over-complicating matters, there is a fifth option which I actually prefer above these four obvious possibilities, which is to bundle your code as a standalone git repository that is added to requirements.txt and installed by pip. We ignore this possibility in this tutorial solely because it requires a deep understanding of pip as well as some subtleties of how the developers at edX go about managing the sizable number of dependencies in the Open edX project.