Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: modifications to remove wanings updated enrollment service #6

Merged
merged 4 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 15 additions & 200 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,221 +1,36 @@
Openedx CMI5 XBlock
#############################

Xblock to play CMI5 content inside Open edX
Xblock to integrate CMI5 content in Open edX. It provides following features

* Ability to upload CMI5 package or cmi5.xml file
* Parses xAPI statements generated by CMI5 content to set grade and track progress of learner
* Ability to push xAPI statements to a configured LRS

Testing with Docker
Setup
********************

This XBlock comes with a Docker test environment ready to build, based on the xblock-sdk workbench. To build and run it::
Install CMI5 XBlock

$ make dev.run
$ pip install openedx-cmi5-xblock

The XBlock SDK Workbench, including this XBlock, will be available on the list of XBlocks at http://localhost:8000
Enable XBlock in Studio

Installation for tutor
**********************
`Settings -> Advanced Settings -> Advanced Module List` add `openedx_cmi5_xblock`

This method works with `tutor <https://github.com/overhangio/tutor/>`__.
Use it in any of the units by adding `CMI5 Module` from Advanced blocks list

First, go to your requirements directory::
Advanced Configuration For sending data to LRS
**********************************************

cd $(tutor config printroot)/env/build/openedx/requirements/
CMI5 Xblock can be configured to push xAPI statements to a Third-party Learning Record Store. To configure that, use these settings

.. code-block:: python

add the `openedx-cmi5-xblock` repo to the `private.txt`::

echo "git+https://github.com/edly-io/openedx-cmi5-xblock.git" >> private.txt


and build a new image::

tutor images build openedx


In your studio, in your desired course, go to Advanced Settings and add `"openedx_cmi5_xblock"` in the Advanced Module List.

If you want to test the cmi5 content, then `here <https://xapi.com/cmi5/example-course-templates/>`__ you can get demo files to test:

Development
***********

There's no need to build a new image, if you just want to play with the xblock.

First, clone the repo in the requirements directory::

cd $(tutor config printroot)/env/build/openedx/requirements/
git clone [email protected]:edly-io/openedx-cmi5-xblock.git


exec to the lms container and install the XBlock::

tutor dev exec -it lms bash
cd ../requirements
pip install -e openedx-cmi5-xblock

If you struggle with lms not displaying your cmi5 content in IFrame, then ``X_FRAME_OPTIONS = "SAMEORIGIN"`` is the settings you need to add a patch for to give access to your lms domain.

Note: This is not the best practice to develop an XBlock, but it works if you don't want to build dev image.


Advanced Configuration For External LRS
***************************************

The cmi5 Xblock may be configured to enable Third-party Learning Record Store to keep record of xapi statements outside of LMS. To configure that, add the following to Tutor by creating a `plugin <https://docs.tutor.overhang.io/plugins/>`__::

hooks.Filters.ENV_PATCHES.add_item(
(
"openedx-common-settings",
"""
XBLOCK_SETTINGS["CMI5XBlock"] = {
"LRS_AUTH_KEY": "<LRS-activity-provider-key>",
"LRS_AUTH_SECRET": "<LRS-secret-key>",
"LRS_ENDPOINT": "<domain>/lrs/<LRS-app-id>/statements/"
# ... other settings
}"""
)
)

Note: This method is for enabling External LRS for CMI5-Xblock in Tutor.


Translating
*************

Internationalization (i18n) is when a program is made aware of multiple languages.
Localization (l10n) is adapting a program to local language and cultural habits.

Use the locale directory to provide internationalized strings for your XBlock project.
For more information on how to enable translations, visit the
`Open edX XBlock tutorial on Internationalization <https://edx.readthedocs.org/projects/xblock-tutorial/en/latest/edx_platform/edx_lms.html>`_.

This cookiecutter template uses `django-statici18n <https://django-statici18n.readthedocs.io/en/latest/>`_
to provide translations to static javascript using ``gettext``.

The included Makefile contains targets for extracting, compiling and validating translatable strings.
The general steps to provide multilingual messages for a Python program (or an XBlock) are:

1. Mark translatable strings.
2. Run i18n tools to create raw message catalogs.
3. Create language specific translations for each message in the catalogs.
4. Use ``gettext`` to translate strings.

1. Mark translatable strings
=============================

Mark translatable strings in python::


from django.utils.translation import ugettext as _

# Translators: This comment will appear in the `.po` file.
message = _("This will be marked.")

See `edx-developer-guide <https://edx.readthedocs.io/projects/edx-developer-guide/en/latest/internationalization/i18n.html#python-source-code>`__
for more information.

You can also use ``gettext`` to mark strings in javascript::


// Translators: This comment will appear in the `.po` file.
var message = gettext("Custom message.");

See `edx-developer-guide <https://edx.readthedocs.io/projects/edx-developer-guide/en/latest/internationalization/i18n.html#javascript-files>`__
for more information.

2. Run i18n tools to create Raw message catalogs
=================================================

This cookiecutter template offers multiple make targets which are shortcuts to
use `edx-i18n-tools <https://github.com/openedx/i18n-tools>`_.

After marking strings as translatable we have to create the raw message catalogs.
These catalogs are created in ``.po`` files. For more information see
`GNU PO file documentation <https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html>`_.
These catalogs can be created by running::


$ make extract_translations

The previous command will create the necessary ``.po`` files under
``openedx-cmi5-xblock/openedx_cmi5_xblock/locale/en/LC_MESSAGES/text.po``.
The ``text.po`` file is created from the ``django-partial.po`` file created by
``django-admin makemessages`` (`makemessages documentation <https://docs.djangoproject.com/en/3.2/topics/i18n/translation/#message-files>`_),
this is why you will not see a ``django-partial.po`` file.

3. Create language specific translations
==============================================

3.1 Add translated strings
---------------------------

After creating the raw message catalogs, all translations should be filled out by the translator.
One or more translators must edit the entries created in the message catalog, i.e. the ``.po`` file(s).
The format of each entry is as follows::

# translator-comments
A. extracted-comments
#: reference…
#, flag…
#| msgid previous-untranslated-string
msgid 'untranslated message'
msgstr 'mensaje traducido (translated message)'

For more information see
`GNU PO file documentation <https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html>`_.

To use translations from transifex use the follow Make target to pull translations::

$ make pull_translations

See `config instructions <https://github.com/openedx/i18n-tools#transifex-commands>`_ for information on how to set up your
transifex credentials.

See `transifex documentation <https://docs.transifex.com/integrations/django>`_ for more details about integrating
django with transiflex.

3.2 Compile translations
-------------------------

Once translations are in place, use the following Make target to compile the translation catalogs ``.po`` into
``.mo`` message files::

$ make compile_translations

The previous command will compile ``.po`` files using
``django-admin compilemessages`` (`compilemessages documentation <https://docs.djangoproject.com/en/3.2/topics/i18n/translation/#compiling-message-files>`_).
After compiling the ``.po`` file(s), ``django-statici18n`` is used to create language specific catalogs. See
``django-statici18n`` `documentation <https://django-statici18n.readthedocs.io/en/latest/>`_ for more information.

To upload translations to transiflex use the follow Make target::

$ make push_translations

See `config instructions <https://github.com/openedx/i18n-tools#transifex-commands>`_ for information on how to set up your
transifex credentials.

See `transifex documentation <https://docs.transifex.com/integrations/django>`_ for more details about integrating
django with transiflex.

**Note:** The ``dev.run`` make target will automatically compile any translations.

**Note:** To check if the source translation files (``.po``) are up-to-date run::

$ make detect_changed_source_translations

4. Use ``gettext`` to translate strings
========================================

Django will automatically use ``gettext`` and the compiled translations to translate strings.

Troubleshooting
****************

If there are any errors compiling ``.po`` files run the following command to validate your ``.po`` files::

$ make validate
}

See `django's i18n troubleshooting documentation
<https://docs.djangoproject.com/en/3.2/topics/i18n/translation/#troubleshooting-gettext-incorrectly-detects-python-format-in-strings-with-percent-signs>`_
for more information.
46 changes: 30 additions & 16 deletions openedx_cmi5_xblock/openedx_cmi5_xblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
from django.utils import timezone
from django.utils.module_loading import import_string
from six import string_types
from web_fragments.fragment import Fragment
from webob import Response
from xblock.completable import CompletableXBlockMixin
from xblock.core import XBlock
from xblock.fields import Boolean, DateTime, Dict, Float, Integer, List, Scope, String
from xblock.fragment import Fragment

from openedx_cmi5_xblock.utils.utility import (
get_request_body,
Expand Down Expand Up @@ -129,6 +129,12 @@ class CMI5XBlock(XBlock, CompletableXBlockMixin):
scope=Scope.content,
)

au_has_any_window = Boolean(
display_name=_('Has one of the AUs launch method is AnyWindow'),
default=False,
scope=Scope.settings
)

has_author_view = True

def author_view(self, context=None):
Expand All @@ -153,7 +159,6 @@ def studio_view(self, context=None):
'field_height': self.fields['height'],
'cmi5_xblock': self
}

studio_context.update(context or {})
template = render_template('static/html/studio.html', studio_context)
frag = Fragment(template)
Expand Down Expand Up @@ -193,16 +198,8 @@ def lrs_endpoint(self, request, _suffix):
"""
credentials = self.get_credentials()

if request.params.get('statementId') and request.method == 'PUT' and credentials["EXTERNAL_LRS_URL"]:
if request.params.get('statementId') and request.method == 'PUT':
statement_data = get_request_body(request)
# send statements to external lrs.
send_xapi_to_external_lrs(
statement_data,
credentials["EXTERNAL_LRS_URL"],
credentials["LRS_AUTH_KEY"],
credentials["LRS_AUTH_SECRET"]
)

lesson_status = statement_data.get('verb').get('display').get('en')
object_categories = statement_data.get('context', {}).get('contextActivities', {}).get('category')

Expand All @@ -216,6 +213,15 @@ def lrs_endpoint(self, request, _suffix):
self.lesson_status = lesson_status
self.emit_completion(1.0)

# send statements to external lrs.
if credentials["EXTERNAL_LRS_URL"]:
send_xapi_to_external_lrs(
statement_data,
credentials["EXTERNAL_LRS_URL"],
credentials["LRS_AUTH_KEY"],
credentials["LRS_AUTH_SECRET"]
)

return Response(status=204)

elif request.params.get('stateId'):
Expand Down Expand Up @@ -355,11 +361,12 @@ def get_grade(self):

def get_erollment_id(self):
"""Retrieves the enrollment ID of the current user for the XBlock's course."""
user_id = self.get_current_user_attr('edx-platform.user_id')
course_id = self.runtime.course_id
django_user = self.runtime.service(self, 'user').get_current_django_user()
course_id = self.scope_ids.usage_id.context_key

try:
enrollment = self.runtime.service(self, 'enrollments').get_active_enrollment_of_user_by_course(
user_id, course_id
enrollment = self.runtime.service(self, 'enrollments').get_active_enrollments_by_course_and_user(
course_id, django_user
)
return enrollment.id
except Exception as err:
Expand Down Expand Up @@ -522,17 +529,24 @@ def update_package_fields(self):
if au_elements:
self.index_page_path = au_elements[0].find('./{prefix}url'.format(prefix=prefix)).text
au_data_list = []
launch_methods = []
for au in au_elements:
au_url = au.find('./{prefix}url'.format(prefix=prefix)).text
au_title = au.find('./{prefix}title/{prefix}langstring'.format(prefix=prefix)).text
launch_method = au.get('launchMethod', 'AnyWindow')
launch_methods.append(launch_method)

au_data = {
'url': au_url,
'url': au_url.strip(),
'title': au_title,
'launch_method': launch_method
}
au_data_list.append(au_data)

self.au_urls = au_data_list
# TODO: Uncomment the code below when we have figured out a workaround of limitations imposed by
# browsers while loading iframe from different domain than host
# self.au_has_any_window = True if 'AnyWindow' in launch_methods else False
else:
self.index_page_path = self.find_relative_file_path('index.html')

Expand Down
16 changes: 16 additions & 0 deletions openedx_cmi5_xblock/static/css/openedx_cmi5_xblock.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,19 @@
.openedx_cmi5_xblock_block p {
cursor: pointer;
}

.cmi5-column {
float: left;
}

.cmi5-nav {
width: 25%;
}

.cmi5-content {
width: 75%;
}

.cmi5-content iframe {
border: 0;
}
Loading
Loading