Badging with Badgr

I want to share my experience with Badgr and the Badging feature. I am using Nutmeg and devstack.

Badgr is now part of Canvas, so some references in docs are broken since those belong to the Canvas domain now. Before diving into Badgr and its usage within the OpenEdx ecosystem, I would recommend to get familiar with the core data classes of the Open Badges specification, such as: Assertions, BadgeClasses, and Profiles. Useful resources for this post:

In order to use Badging with Badgr, I will guide you with help of the following diagram which is based on Course Completion Badge generation.

Let’s set up the Badgr Server

Before the question arises. The Issuer term that you will find in the Badgr Website is equivalent to the Profile class in the OpenBadges specification. The issuer issues badges.

  1. Go to the Badgr Website and create an account. In case you sign up with Google, Facebook…, etc, you still need to set up a password for next steps. You will be able to set/change the password in Account Settings.
  2. You can create the issuer manually. However, I think that It is worth creating it using the Badgr API. If using the API you will get the result[0].entityId from the response of the post request.
{
    "status": {
        "success": true,
        "description": "ok"
    },
    "result": [
        {
            "entityType": "Issuer",
            "entityId": "somethinglikethis123ABC",
            ...
        }
    ...
}

If you created the issuer manually, then select the issuer in the website and you will find the entityId in the URL https://badgr.com/issuers/somethinglikethis123ABC/badges. The entityId value is going to be used to configure Badgr in our LMS.

All right, you are all done. You could now create Badges and Assertions with the API to fully understand what the LMS does and to be aware of the required fields for each request. But, there is really no need to create Badges and Assertions since the LMS will create them for us.

Configure Badging with Badgr server.

  1. Make sure Certificates are enable.

COURSE_CERT_AWARDED signal is the trigger for Badges awarded from Course Completion events, therefore the following functions won’t work if Certificate generation isn’t enabled. No certificates, then no these type of Badges.

  1. Enable and Configure OPENBADGES

Edit lms.yml with the following variables. Same credentials yo use in the Badgr website.

BADGR_USERNAME: "admin@mail"
BADGR_PASSWORD: "password_used_by_admin@mail"
BADGR_TOKENS_CACHE_KEY: "secret-cache-key"
BADGR_BASE_URL: "https://api.badgr.io"
BADGR_ISSUER_SLUG: "somethinglikethis123ABC"

Edit both lms.yml and studio.yml with the following variables:

FEATURES:
    ENABLE_OPENBADGES: true

Now go to Django admin to set something as default for the CourseCompleteImageConfiguration model. http://localhost:18000/admin/badges/coursecompleteimageconfiguration/. I set the verified mode as default and also updated the Icon. See Issue 1 in the Issues section of this post to find out why this procedure/workaround is required.

At this point we are ready to support Badges in our platform.

Configure and get a Course Completion Badge.

This type of badge is configured by default, so the only remaining work for us is to configure certificates for a course. Once a student gets a passing grade, then the signals which were mentioned in step 3 will generate the Badge.

  1. Create a graded course and activate the certificate. You might need to add the course as Verified in eComm. I have added it as Audit and Verified. Don’t forget to preview the certificate to check everything is working as expected.

  2. Use a user with a real email to enroll in the verified track and complete the graded subsections. The idea of using an existing email is to see our Badge once we earned. See Issue 3.

If everything is well configured, then the LMS/Badgr server implementation is going to do all the magic.

What happens behind Course Completion?

Once the user reached a passing grade, then COURSE_CERT_AWARDED signal is emitted and therefore captured by create_course_badge.

BadgeClass is created in LMS here get_badge_class. It is created once the first student completes the course.

BadgeClass is created in Badgr here _ensure_badge_created, before awarding --creating the assertion for-- the first student.

Assertion is created for both LMS and Badgr here _create_assertion. Unlike BadgeClasses, An assertion is first created for Badgr so if something goes wrong, then it is not created for the LMS.

Where can I see/share my badge?
Ok… I got a badge. Great. How do I show off now?.

  1. Go to Profile (make sure you do not have a limited profile, adding an age >21 is enough) and click on the accomplishments tab. You should see something like this.

  2. Create an account in Badgr with the user email. Badgr does the association and once you register and enter, you will see something like this in your Backpack:

Issues:

  1. Certificate preview fails

Description: Certificate preview fails in Studio once OPENBADGES is enabled.
Reason: When previewing the certificate, a BadgeClass is created for audit mode and if there is no default CourseCompleteImageConfiguration, then the image_for_node method fails as query fails finding a default.
Quick fix: Define a default CourseCompleteImageConfiguration, then once you preview the Certificate a new BadgeClass will be created, in my case:
image
Appropriate Fix: I think that a badge class shouldn’t be created for audit mode while previewing the certificate trough studio.

  1. Assertions for Course Event Badges are not created.

Description: Student are not being awarded with Course Event Badges as the API request is failing.
Reason: Badgr server requires the Evidence which is not been provided. For course completion, this is the URL to the certificate which has public access.
Fix: WIP (According to the OpenBadges specification, the evidence field could receive multiple values, so it shouldn’t be a problem to pass the URLs of all certificates related to the badge)

image

  1. Badges are associated to email used in OpenEdx account.

Description: More than an issue, this is a warning. Once a user is awarded with a Badge, the Assertion/Award is created in Badgr with the email used by the user in the LMS. Therefore, if user is expecting to use another email to keep his/her Badges, then user should be aware of this.
Reason: This is the default behavior.
Fix: Create an account in Badgr to get your Badge and its public URL, with this URL you will be able to create a Badge in another account or Badging platform. The image can be downloaded from the LMS (Profile β†’ Accomplishments)

4 Likes

@jacatove Thanks for the write-up. We have the configurations in our LMS setup as specified (ENABLE_OPENBADGES, Badgr credentials, slug, etc…) and changed the image and set the mode as the default.

We have the certificate setup and can preview it just fine.

We created a class with the content of just having a single multiple choice test that is set as the final exam.

We complete the course with a user who’s LMS email address is the same as that user’s badgr backpack email address:

We do not see the badge being awarded in the Issuer page, nor in the user’s Badgr backpack.

So should we expect to see the badge before the certificate is actually generated (is there a way to make certificates be produced immediately so we don’t have to wait a day to test?)? That is, as soon as we get a passing grade on the course (set to self-paced), would we expect to see a badge?

Thanks for your time and this write-up.

Have you tried enable-automatic-certificate-generation?.

This is what I have:
image

@jacatove Yeah we have it enabled:

@jacatove Looking at the logs, we see this (notice the function you mentioned in your post is where it throws)

2022-11-09 19:07:56,115 INFO 11 [celery.app.trace] [user None] [ip None] trace.py:131 - Task lms.djangoapps.grades.tasks.recalculate_subsection_grade_v3[eea15c22-bb6b-4af7-b6c0-fb8f72da44dc] succeeded in 0.26011689299775753s: None                                               β”‚
β”‚ 2022-11-09 19:07:58,211 INFO 11 [openedx_events.tooling] [user None] [ip None] tooling.py:160 - Responses of the Open edX Event <org.openedx.learning.certificate.changed.v1>:                                                                                                       β”‚
β”‚ []                                                                                                                                                                                                                                                                                   β”‚
β”‚ 2022-11-09 19:07:58,969 INFO 11 [botocore.vendored.requests.packages.urllib3.connectionpool] [user None] [ip None] connectionpool.py:734 - Starting new HTTPS connection (1): s3.amazonaws.com                                                                                       β”‚
β”‚ 2022-11-09 19:07:59,662 ERROR 11 [django.dispatch] [user None] [ip None] dispatcher.py:214 - Error calling create_course_badge in Signal.send_robust() (This backend doesn't support absolute paths.)                                                                                β”‚
β”‚ Traceback (most recent call last):                                                                                                                                                                                                                                                   β”‚
β”‚   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/query.py", line 581, in get_or_create                                                                                                                                                                             β”‚
β”‚     return self.get(**kwargs), False                                                                                                                                                                                                                                                 β”‚
β”‚   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/query.py", line 435, in get                                                                                                                                                                                       β”‚
β”‚     raise self.model.DoesNotExist(                                                                                                                                                                                                                                                   β”‚
β”‚ lms.djangoapps.certificates.models.GeneratedCertificate.DoesNotExist: GeneratedCertificate matching query does not exist.                                                                                                                                                            β”‚
β”‚                                                                                                                                                                                                                                                                                      β”‚
β”‚ During handling of the above exception, another exception occurred:                                                                                                                                                                                                                  β”‚
β”‚                                                                                                                                                                                                                                                                                      β”‚
β”‚ Traceback (most recent call last):                                                                                                                                                                                                                                                   β”‚
β”‚   File "/openedx/venv/lib/python3.8/site-packages/django/dispatch/dispatcher.py", line 212, in send_robust                                                                                                                                                                           β”‚
β”‚     response = receiver(signal=self, sender=sender, **named)                                                                                                                                                                                                                         β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/certificates/models.py", line 1241, in create_course_badge                                                                                                                                                                              β”‚
β”‚     course_badge_check(user, course_key)                                                                                                                                                                                                                                             β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/utils.py", line 27, in wrapped                                                                                                                                                                                                   β”‚
β”‚     return function(*args, **kwargs)                                                                                                                                                                                                                                                 β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/events/course_complete.py", line 129, in course_badge_check                                                                                                                                                                      β”‚
β”‚     badge_class.award(user, evidence_url=evidence)                                                                                                                                                                                                                                   β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/models.py", line 131, in award                                                                                                                                                                                                   β”‚
β”‚     return self.backend.award(self, user, evidence_url=evidence_url)  # lint-amnesty, pylint: disable=no-member                                                                                                                                                                      β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/backends/badgr.py", line 323, in award                                                                                                                                                                                           β”‚
β”‚     self._ensure_badge_created(badge_class)                                                                                                                                                                                                                                          β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/backends/badgr.py", line 316, in _ensure_badge_created                                                                                                                                                                           β”‚
β”‚     self._create_badge(badge_class)                                                                                                                                                                                                                                                  β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/backends/badgr.py", line 120, in _create_badge                                                                                                                                                                                   β”‚
β”‚     with open(image.path, 'rb') as image_file:                                                                                                                                                                                                                                       β”‚
β”‚   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/fields/files.py", line 59, in path                                                                                                                                                                                β”‚
β”‚     return self.storage.path(self.name)                                                                                                                                                                                                                                              β”‚
β”‚   File "/openedx/venv/lib/python3.8/site-packages/django/core/files/storage.py", line 128, in path                                                                                                                                                                                   β”‚
β”‚     raise NotImplementedError("This backend doesn't support absolute paths.")                                                                                                                                                                                                        β”‚
β”‚ NotImplementedError: This backend doesn't support absolute paths.                                                                                                                                                                                                                    β”‚
β”‚ 2022-11-09 19:08:00,075 ERROR 11 [django.dispatch] [user None] [ip None] dispatcher.py:214 - Error calling create_completion_badge in Signal.send_robust() (This backend doesn't support absolute paths.)                                                                            β”‚
β”‚ Traceback (most recent call last):                                                                                                                                                                                                                                                   β”‚
β”‚   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/query.py", line 581, in get_or_create                                                                                                                                                                             β”‚
β”‚     return self.get(**kwargs), False                                                                                                                                                                                                                                                 β”‚
β”‚   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/query.py", line 435, in get                                                                                                                                                                                       β”‚
β”‚     raise self.model.DoesNotExist(                                                                                                                                                                                                                                                   β”‚
β”‚ lms.djangoapps.certificates.models.GeneratedCertificate.DoesNotExist: GeneratedCertificate matching query does not exist.                                                                                                                                                            β”‚
β”‚                                                                                                                                                                                                                                                                                      β”‚
β”‚ During handling of the above exception, another exception occurred:

Traceback (most recent call last):                                                                                                                                                                                                                                                   β”‚
β”‚   File "/openedx/venv/lib/python3.8/site-packages/django/dispatch/dispatcher.py", line 212, in send_robust                                                                                                                                                                           β”‚
β”‚     response = receiver(signal=self, sender=sender, **named)                                                                                                                                                                                                                         β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/certificates/models.py", line 1249, in create_completion_badge                                                                                                                                                                          β”‚
β”‚     completion_check(user)                                                                                                                                                                                                                                                           β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/utils.py", line 27, in wrapped                                                                                                                                                                                                   β”‚
β”‚     return function(*args, **kwargs)                                                                                                                                                                                                                                                 β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/events/course_meta.py", line 57, in completion_check                                                                                                                                                                             β”‚
β”‚     award_badge(config, certificates, user)                                                                                                                                                                                                                                          β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/events/course_meta.py", line 32, in award_badge                                                                                                                                                                                  β”‚
β”‚     badge_class.award(user)                                                                                                                                                                                                                                                          β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/models.py", line 131, in award                                                                                                                                                                                                   β”‚
β”‚     return self.backend.award(self, user, evidence_url=evidence_url)  # lint-amnesty, pylint: disable=no-member                                                                                                                                                                      β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/backends/badgr.py", line 323, in award                                                                                                                                                                                           β”‚
β”‚     self._ensure_badge_created(badge_class)                                                                                                                                                                                                                                          β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/backends/badgr.py", line 316, in _ensure_badge_created                                                                                                                                                                           β”‚
β”‚     self._create_badge(badge_class)                                                                                                                                                                                                                                                  β”‚
β”‚   File "/openedx/edx-platform/lms/djangoapps/badges/backends/badgr.py", line 120, in _create_badge                                                                                                                                                                                   β”‚
β”‚     with open(image.path, 'rb') as image_file:                                                                                                                                                                                                                                       β”‚
β”‚   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/fields/files.py", line 59, in path                                                                                                                                                                                β”‚
β”‚     return self.storage.path(self.name)                                                                                                                                                                                                                                              β”‚
β”‚   File "/openedx/venv/lib/python3.8/site-packages/django/core/files/storage.py", line 128, in path                                                                                                                                                                                   β”‚
β”‚     raise NotImplementedError("This backend doesn't support absolute paths.")                                                                                                                                                                                                        β”‚
β”‚ NotImplementedError: This backend doesn't support absolute paths.                                                                                                                                                                                                                    β”‚
β”‚ 2022-11-09 19:08:00,416 INFO 11 [openedx_events.tooling] [user None] [ip None] tooling.py:160 - Responses of the Open edX Event <org.openedx.learning.certificate.created.v1>:                                                                                                       β”‚
β”‚ []                                                                                                                                                                                                                                                                                   β”‚
β”‚ 2022-11-09 19:08:00,425 INFO 11 [lms.djangoapps.certificates.generation] [user None] [ip None] generation.py:98 - Generated certificate with status downloadable, mode honor and grade 1.0 for 7 : course-v1:FakeOrg+BDG109+2022_T1. Certificate was created.                        β”‚
β”‚ 2022-11-09 19:08:00,433 INFO 11 [tracking] [user None] [ip None] logger.py:41 - {"name": "edx.certificate.created", "context": {"org_id": "FakeOrg", "course_id": "course-v1:FakeOrg+BDG109+2022_T1"}, "username": "", "session": "", "ip": "", "agent": "", "host": "", "referer":  β”‚
β”‚ 2022-11-09 19:08:00,436 INFO 11 [celery.app.trace] [user None] [ip None] trace.py:131 - Task lms.djangoapps.certificates.tasks.generate_certificate[55b6da8f-cd77-46d1-8a24-d13a70fc024a] succeeded in 2.262843096999859s: None                                                      β”‚
β”‚ 2022-11-09 19:08:25,616 INFO 1 [celery.worker.control] [user None] [ip None] control.py:310 - sync with celery@edx.lms.core.default.%lms-worker-75f89cbb6d-svkbq                                                                                                                     β”‚
β”‚ 2022-11-09 19:09:13,068 INFO 1 [celery.worker.strategy] [user None] [ip None] strategy.py:161 - Task lms.djangoapps.instructor_task.tasks.generate_certificates[0dd500d6-59fc-485b-a73d-98239fd2e84d] received                                                                       β”‚
β”‚ 2022-11-09 19:09:13,072 INFO 11 [edx.celery.task] [user None] [ip None] tasks.py:278 - Task: 0dd500d6-59fc-485b-a73d-98239fd2e84d, InstructorTask ID: 2, Task type: certificates generated, Preparing for task execution                                                             β”‚
β”‚ 2022-11-09 19:09:13,106 INFO 11 [edx.celery.task] [user None] [ip None] runner.py:108 - Task: 0dd500d6-59fc-485b-a73d-98239fd2e84d, InstructorTask ID: 2, Course: course-v1:FakeOrg+BDG109+2022_T1, Input: {'statuses_to_regenerate': ['downloadable']}, Starting update (nothing ce β”‚
β”‚ 2022-11-09 19:09:13,116 INFO 11 [lms.djangoapps.instructor_task.tasks_helper.certs] [user None] [ip None] certs.py:64 - About to attempt certificate generation for 1 users in course course-v1:FakeOrg+BDG109+2022_T1. The student_set is None and statuses_to_regenerate is ['down β”‚
β”‚ 2022-11-09 19:09:13,119 INFO 11 [lms.djangoapps.instructor_task.tasks_helper.certs] [user None] [ip None] certs.py:75 - Attempt will be made to generate a course certificate for 7 : course-v1:FakeOrg+BDG109+2022_T1.                                                              β”‚
β”‚ 2022-11-09 19:09:13,124 INFO 11 [lms.djangoapps.certificates.generation_handler] [user None] [ip None] generation_handler.py:54 - Attempt will be made to generate course certificate for user 7 : course-v1:FakeOrg+BDG109+2022_T1                                                  β”‚
β”‚ 2022-11-09 19:09:13,156 INFO 11 [lms.djangoapps.certificates.generation_handler] [user None] [ip None] generation_handler.py:220 - 7 : course-v1:FakeOrg+BDG109+2022_T1 is eligible for a certificate without requiring a verified ID. Skipping results of the ID verification check β”‚
β”‚ 2022-11-09 19:09:13,161 INFO 11 [lms.djangoapps.certificates.generation_handler] [user None] [ip None] generation_handler.py:367 - Certificate with status downloadable already exists for 7 : course-v1:FakeOrg+BDG109+2022_T1, and is not eligible for generation. Certificate can β”‚
β”‚ 2022-11-09 19:09:13,161 INFO 11 [lms.djangoapps.certificates.generation_handler] [user None] [ip None] generation_handler.py:184 - One of the common checks failed. Certificate cannot be generated for 7 : course-v1:FakeOrg+BDG109+2022_T1.                                        β”‚
β”‚ 2022-11-09 19:09:13,177 INFO 11 [edx.celery.task] [user None] [ip None] runner.py:126 - Task: 0dd500d6-59fc-485b-a73d-98239fd2e84d, InstructorTask ID: 2, Course: course-v1:FakeOrg+BDG109+2022_T1, Input: {'statuses_to_regenerate': ['downloadable']}, Task type: certificates gen β”‚β”‚ 2022-11-09 19:09:13,195 INFO 11 [celery.app.trace] [user None] [ip None] trace.py:131 - Task lms.djangoapps.instructor_task.tasks.generate_certificates[0dd500d6-59fc-485b-a73d-98239fd2e84d] succeeded in 0.10948053199899732s: {'action_name': 'certificates generated', 'attempte β”‚β”‚ 2022-11-09 19:34:15,565 INFO 1 [celery.worker.strategy] [user None] [ip None] strategy.py:161 - Task openedx.core.djangoapps.content.block_structure.tasks.update_course_in_cache_v2[978f5250-908b-472c-8a23-c850a7157463] received                                                  β”‚β”‚ 2022-11-09 19:34:45,721 INFO 11 [openedx.core.djangoapps.content.block_structure.store] [user None] [ip None] store.py:165 - BlockStructure: Added to cache; block-v1:FakeOrg+BDG109+2022_T1+type@course+block@course, size: 1895                                                    β”‚β”‚ 2022-11-09 19:34:45,723 INFO 11 [celery.app.trace] [user None] [ip None] trace.py:131 - Task openedx.core.djangoapps.content.block_structure.tasks.update_course_in_cache_v2[978f5250-908b-472c-8a23-c850a7157463] succeeded in 0.05410869000115781s: None  

@jacatove it looks like it is throwing here on the file path (in lms > djangoapps > badges > backends > badgr.py):

Is it possible to see the response of the request result.json()?. Maybe something is going wrong with the request and debugging it might show you the issue.

Not exactly sure how I would go in and see that.