Skip to content

Commit

Permalink
🌅
Browse files Browse the repository at this point in the history
  • Loading branch information
regisb committed Oct 13, 2021
0 parents commit 0d65c57
Show file tree
Hide file tree
Showing 48 changed files with 784 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Sync with private repo

on:
push:
branches: [ master ]

jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Add remote
run: git remote add overhangio https://${{ secrets.GIT_USERNAME }}:${{ secrets.GIT_PASSWORD }}@git.overhang.io/core/tutor-richie.git
- name: Push
run: git push overhangio master
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.*.swp
!.gitignore
TODO
__pycache__
*.egg-info/
/build/
/dist/
10 changes: 10 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
variables:
TUTOR_PLUGIN: richie
TUTOR_IMAGES: richie
TUTOR_PYPI_PACKAGE: tutor-richie
OPENEDX_RELEASE: lilac
GITHUB_REPO: overhangio/tutor-richie

include:
- project: 'community/tutor-ci'
file: 'plugin-gitlab-ci.yml'
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
recursive-include tutorrichie/patches *
recursive-include tutorrichie/templates *
82 changes: 82 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
Richie plugin for `Tutor <https://docs.tutor.overhang.io>`__
============================================================

This is a plugin to integrate `Richie <https://richie.education/>`__, the learning portal CMS, with `Open edX <https://open.edx.org>`__. The integration takes the form of a `Tutor <https://docs.tutor.overhang.io>`__ plugin.

Installation
------------

::

pip install tutor-richie
tutor plugins enable richie

Running the Richie plugin will require that you rebuild the "openedx" Docker image::

tutor config save
tutor images build openedx

This step is necessary to install the Richie connector app in edx-platform.

Then, the platform can be launched as usual with::

tutor local quickstart

You will be able to access your course catalog at http(s)://courses.LMS_HOST. In development, this url will be http://courses.local.overhang.io.

Gettting started
----------------

Once your Richie platform is up and running, you will quickly realize that your learning portal is empty. This is because you should first create the corresponding courses and organizations from inside Richie. To do so, start by creating a super user::

tutor local run richie ./sandbox/manage.py createsuperuser

You can then use the credentials you just created at http(s)://yourrichiehost/admin. In development, this is http://courses.local.overhang.io/admin.

Then, refer to the official `Richie documentation <https://richie.education/docs/quick-start>`__ to learn how to create courses and organizations.

You may also want to fill your learning portal with a demo site -- but be careful not to run this command in production, as it will be difficult to get rid of the demo site afterwards::

# WARNING: do not attempt this in production!
tutor local run richie ./sandbox/manage.py create_demo_site --force

Configuration
-------------

This Tutor plugin comes with a few configuration settings:

- ``RICHIE_RELEASE_VERSION`` (default: ``"v2.8.2"``)
- ``RICHIE_HOST`` (default: ``"courses.{{ LMS_HOST }}"``)
- ``RICHIE_MYSQL_DATABASE`` (default: ``"richie"``)
- ``RICHIE_MYSQL_USERNAME`` (default: ``"richie"``)
- ``RICHIE_ELASTICSEARCH_INDICES_PREFIX`` (default: ``"richie"``)

These defaults should be enough for most users. To modify any one of them, run::

tutor config save --set RICHIE_SETTING_NAME=myvalue

For instance, to customize the domain name at which Richie will run::

tutor config save --set "RICHIE_HOST=mysubdomain.{{ LMS_HOST }}"

Development
-----------

Bind-mount volume::

tutor dev bindmount richie /app/richie

Then, run a development server::

tutor dev runserver --volume=/app/richie richie

The Richie development server will be available at http://courses.local.overhang.io:8003.

License
-------

This software is licensed under the terms of the `AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.en.html>`__. It was developed and is being actively maintained thanks to the sponsorship of `France Université Numérique <https://github.com/openfun>`__.

.. image:: https://www.fun-mooc.fr/static/richie/images/logo.png
:alt: France Université Numérique
:target: https://fun-mooc.fr
4 changes: 4 additions & 0 deletions contrib/edx-platform/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Richie connector plugin app for Open edX
========================================

This is an `Open edX plugin <https://github.com/edx/edx-django-utils/blob/master/edx_django_utils/plugins/README.rst>`__ to facilitate the integration of a `Richie <https://richie.education>`__ learning portal. Its main feature is automatically syncing course properties when they are updated in the Open edX studio.
Empty file.
38 changes: 38 additions & 0 deletions contrib/edx-platform/richie/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from django.apps import AppConfig
from edx_django_utils.plugins.constants import PluginSettings, PluginURLs
from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType


class RichieAppConfig(AppConfig):
name = "richie"
verbose_name = "Richie course catalog connector"

# Open edX plugin docs: https://github.com/edx/edx-django-utils/blob/master/edx_django_utils/plugins/README.rst
plugin_app = {
PluginURLs.CONFIG: {
ProjectType.LMS: {
PluginURLs.NAMESPACE: "richie",
PluginURLs.REGEX: r"^richie/",
PluginURLs.RELATIVE_PATH: "urls",
}
},
PluginSettings.CONFIG: {
ProjectType.LMS: {
SettingsType.COMMON: {
PluginSettings.RELATIVE_PATH: "settings.lms",
},
},
ProjectType.CMS: {
SettingsType.COMMON: {
PluginSettings.RELATIVE_PATH: "settings.cms",
},
},
},
}

def ready(self):
"""
Connect signal handlers.
"""
# pylint: disable=unused-import
from . import signals
Empty file.
7 changes: 7 additions & 0 deletions contrib/edx-platform/richie/settings/cms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
def plugin_settings(settings):
"""Common settings for Richie catalog connector"""
settings.RICHIE_COURSE_HOOK = {
"secret": "richiesecret",
"url": "http://richie:8000/api/v1.0/course-runs-sync/",
"timeout": 3,
}
3 changes: 3 additions & 0 deletions contrib/edx-platform/richie/settings/lms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def plugin_settings(settings):
"""Common settings for Richie catalog connector"""
settings.RICHIE_ROOT_URL = "http://myrichie"
12 changes: 12 additions & 0 deletions contrib/edx-platform/richie/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.dispatch import receiver
from xmodule.modulestore.django import SignalHandler

from .sync import sync_course_from_key


@receiver(SignalHandler.course_published, dispatch_uid="update_course_on_publish")
def update_course_on_publish(sender, course_key, **kwargs):
"""
Synchronize course properties with Richie catalog.
"""
sync_course_from_key(course_key)
71 changes: 71 additions & 0 deletions contrib/edx-platform/richie/sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import hashlib
import hmac
import json
import logging


from django.conf import settings
import requests
import requests.exceptions
from xmodule.modulestore.django import modulestore

logger = logging.getLogger(__name__)


def sync_all_courses():
"""
Synchronize all courses with Richie.
"""
for course in modulestore().get_courses():
sync_course(course)


def sync_course_from_key(course_key):
return sync_course(modulestore().get_course(course_key))


def sync_course(course):
"""
Synchronize an Open edX course with a Richie instance.
Note that only the course settings are synchronized, and not the actual
course contents or description. This function always succeeds, even when the
request fails.
"""
enrollment_start = course.enrollment_start and course.enrollment_start.isoformat()
enrollment_end = course.enrollment_end and course.enrollment_end.isoformat()
data = {
"resource_link": "{}/courses/{}/course".format(
settings.LMS_ROOT_URL, course.id
),
"start": course.start and course.start.isoformat(),
"end": course.end and course.end.isoformat(),
"enrollment_start": enrollment_start,
"enrollment_end": enrollment_end,
"languages": [course.language or settings.LANGUAGE_CODE],
}

signature = hmac.new(
settings.RICHIE_COURSE_HOOK["secret"].encode("utf-8"),
msg=json.dumps(data).encode("utf-8"),
digestmod=hashlib.sha256,
).hexdigest()

try:
response = requests.post(
settings.RICHIE_COURSE_HOOK["url"],
json=data,
headers={"Authorization": "SIG-HMAC-SHA256 {:s}".format(signature)},
timeout=settings.RICHIE_COURSE_HOOK["timeout"],
)
except requests.exceptions.Timeout:
logger.error(
f"Could not synchronize course {course.id} with Richie. Response timeout"
)
return
if response.status_code >= 400:
logger.error(
f"Could not synchronize course {course.id} with Richie. Response: {response.content.decode()}"
)
else:
logger.info(f"Successfuly synchronized course {course.id} with Richie")
7 changes: 7 additions & 0 deletions contrib/edx-platform/richie/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.urls import path

from . import views

urlpatterns = [
path("<path:subpath>", views.redirect_to_richie, name="redirect_to_richie"),
]
15 changes: 15 additions & 0 deletions contrib/edx-platform/richie/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from urllib.parse import urljoin


from django.conf import settings
from django.shortcuts import redirect


def redirect_to_richie(request, subpath):
"""
Redirect to Richie catalog.
This view is used after login from Richie, with a "?next=richie/en"
parameter.
"""
return redirect(urljoin(settings.RICHIE_ROOT_URL, subpath))
49 changes: 49 additions & 0 deletions contrib/edx-platform/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import io
import os
from setuptools import setup, find_packages

HERE = os.path.abspath(os.path.dirname(__file__))


def load_readme():
with io.open(os.path.join(HERE, "README.rst"), "rt", encoding="utf8") as f:
return f.read()


setup(
name="richie-edx-platform",
version="0.0.1",
url="https://github.com/overhangio/tutor-richie/",
project_urls={
"Code": "https://github.com/overhangio/tutor-richie/tree/master/contrib/edx-platform",
"Issue tracker": "https://github.com/overhangio/tutor-richie/issues",
},
license="AGPLv3",
author="Overhang.IO",
description="Open edX plugin app for integration with a Richie catalog",
long_description=load_readme(),
packages=find_packages(exclude=["tests*"]),
include_package_data=True,
entry_points={
"lms.djangoapp": [
"richie = richie.apps:RichieAppConfig",
],
"cms.djangoapp": [
"richie = richie.apps:RichieAppConfig",
],
},
python_requires=">=3.5",
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Affero General Public License v3",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
)
61 changes: 61 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import io
import os
from setuptools import setup, find_packages

HERE = os.path.abspath(os.path.dirname(__file__))


def load_readme():
with io.open(os.path.join(HERE, "README.rst"), "rt", encoding="utf8") as f:
return f.read()


def load_about():
about = {}
with io.open(
os.path.join(HERE, "tutorrichie", "__about__.py"),
"rt",
encoding="utf-8",
) as f:
exec(f.read(), about) # pylint: disable=exec-used
return about


ABOUT = load_about()


setup(
name="tutor-richie",
version=ABOUT["__version__"],
url="https://github.com/overhangio/tutor-richie",
project_urls={
"Code": "https://github.com/overhangio/tutor-richie",
"Issue tracker": "https://github.com/overhangio/tutor-richie/issues",
},
license="AGPLv3",
author="Overhang.IO",
description="richie plugin for Tutor",
long_description=load_readme(),
packages=find_packages(exclude=["tests*", "contrib*"]),
include_package_data=True,
python_requires=">=3.5",
install_requires=["tutor>=12.0.0,<13.0.0"],
entry_points={
"tutor.plugin.v0": [
"richie = tutorrichie.plugin"
]
},
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Affero General Public License v3",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
)
Loading

0 comments on commit 0d65c57

Please sign in to comment.