diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dff637..90bb83c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Note: Breaking changes between versions are indicated by "💥". ## Unreleased +- [Feature] Add `settheme` command to easily assign a theme to a domain name - [Improvement] Modify nginx access logs to include request scheme and server name (plugin developers should use the "tutor" log format) - [Bugfix] Fix DNS resolution of restarted service - [Feature] Restart multiple services with `local restart` diff --git a/docs/dev.rst b/docs/dev.rst index d605ef4..804858d 100644 --- a/docs/dev.rst +++ b/docs/dev.rst @@ -108,6 +108,8 @@ To debug a local edx-platform repository, add a ``import ipdb; ipdb.set_trace()` tutor dev runserver -v /path/to/edx-platform:/openedx/edx-platform lms +.. _theming: + Customised themes ----------------- @@ -122,9 +124,9 @@ Then, run a local webserver:: tutor dev runserver lms -The LMS can then be accessed at http://localhost:8000. - -Then, follow the `Open edX documentation to apply your themes `_. You will not have to modify the ``lms.env.json``/``cms.env.json`` files; just follow the instructions to add a site theme in http://localhost:8000/admin (starting from step 3). +The LMS can then be accessed at http://localhost:8000. You will then have to :ref:`enable that theme ` for the development domain names:: + + tutor dev settheme mythemename localhost:8000 localhost:8001 Watch the themes folders for changes (in a different terminal):: diff --git a/docs/local.rst b/docs/local.rst index 1180502..9cc8c6d 100644 --- a/docs/local.rst +++ b/docs/local.rst @@ -113,6 +113,22 @@ After a fresh installation, your platform will not have a single course. To impo tutor local importdemocourse +.. _settheme: + +Setting a new theme +~~~~~~~~~~~~~~~~~~~ + +The default Open edX theme is rather bland, so Tutor makes it easy to switch to a different theme:: + + tutor local settheme mythemename localhost + +Notice the "localhost" argument: in Open edX, themes are assigned per domain name. That means that your custom theme will only be visible if you access your platform at http://localhost. So you might want to run this command with all possible domain names. For instance, to assign the pre-packaged "edx.org" theme to both the LMS and the studio, locally and in production, run:: + + tutor local settheme edx.org localhost studio.localhost \ + $(tutor config printvalue LMS_HOST) $(tutor config printvalue CMS_HOST) + +The following themes are available out of the box: "dark-theme", "edge.edx.org", "edx.org", "open-edx", "red-theme", "stanford-style". We also developed `Indigo, a beautiful, customizable theme which you can check out `__. + Running arbitrary ``manage.py`` commands ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tutor/commands/compose.py b/tutor/commands/compose.py index c8cc76e..5bb5bd5 100644 --- a/tutor/commands/compose.py +++ b/tutor/commands/compose.py @@ -173,6 +173,19 @@ def createuser(context, superuser, staff, password, name, email): runner.exec("lms", command) +@click.command( + help="Set a theme for a given domain name. To reset to the default theme , use 'default' as the theme name." +) +@click.argument("theme_name") +@click.argument("domain_names", metavar="domain_name", nargs=-1) +@click.pass_obj +def settheme(context, theme_name, domain_names): + config = tutor_config.load(context.root) + runner = ScriptRunner(context.root, config, context.docker_compose) + for domain_name in domain_names: + scripts.set_theme(theme_name, domain_name, runner) + + @click.command(help="Import the demo course") @click.pass_obj def importdemocourse(context): @@ -194,4 +207,5 @@ def add_commands(command_group): command_group.add_command(logs) command_group.add_command(createuser) command_group.add_command(importdemocourse) + command_group.add_command(settheme) # command_group.add_command(run_hook) # Disabled for now diff --git a/tutor/commands/k8s.py b/tutor/commands/k8s.py index 9327657..bffa58d 100644 --- a/tutor/commands/k8s.py +++ b/tutor/commands/k8s.py @@ -131,6 +131,7 @@ def createuser(context, superuser, staff, password, name, email): command = scripts.create_user_command( superuser, staff, name, email, password=password ) + # This needs to be interactive in case the user needs to type a password kubectl_exec(config, "lms", command, attach=True) @@ -143,6 +144,19 @@ def importdemocourse(context): scripts.import_demo_course(runner) +@click.command( + help="Set a theme for a given domain name. To reset to the default theme , use 'default' as the theme name." +) +@click.argument("theme_name") +@click.argument("domain_names", metavar="domain_name", nargs=-1) +@click.pass_obj +def settheme(context, theme_name, domain_names): + config = tutor_config.load(context.root) + runner = K8sScriptRunner(context.root, config) + for domain_name in domain_names: + scripts.set_theme(theme_name, domain_name, runner) + + @click.command(name="exec", help="Execute a command in a pod of the given application") @click.argument("service") @click.argument("command") @@ -229,5 +243,6 @@ k8s.add_command(delete) k8s.add_command(init) k8s.add_command(createuser) k8s.add_command(importdemocourse) +k8s.add_command(settheme) k8s.add_command(exec_command) k8s.add_command(logs) diff --git a/tutor/scripts.py b/tutor/scripts.py index ea1b7ed..5513416 100644 --- a/tutor/scripts.py +++ b/tutor/scripts.py @@ -3,6 +3,11 @@ from . import exceptions from . import fmt from . import plugins +BASE_OPENEDX_COMMAND = """ +export DJANGO_SETTINGS_MODULE=$SERVICE_VARIANT.envs.$SETTINGS +echo "Loading settings $DJANGO_SETTINGS_MODULE" +""" + class BaseRunner: def __init__(self, root, config): @@ -59,10 +64,7 @@ def initialise(runner): def create_user_command(superuser, staff, username, email, password=None): - command = """ -export DJANGO_SETTINGS_MODULE=$SERVICE_VARIANT.envs.$SETTINGS -echo "Loading settings $DJANGO_SETTINGS_MODULE" -""" + command = BASE_OPENEDX_COMMAND opts = "" if superuser: @@ -74,7 +76,10 @@ echo "Loading settings $DJANGO_SETTINGS_MODULE" """ if password: command += """ -./manage.py lms shell -c "from django.contrib.auth import get_user_model; u = get_user_model().objects.get(username='{username}'); u.set_password('{password}'); u.save()" +./manage.py lms shell -c "from django.contrib.auth import get_user_model +u = get_user_model().objects.get(username='{username}') +u.set_password('{password}') +u.save()" """ else: command += """ @@ -87,3 +92,21 @@ echo "Loading settings $DJANGO_SETTINGS_MODULE" def import_demo_course(runner): runner.check_service_is_activated("cms") runner.run("cms", "hooks", "cms", "importdemocourse") + + +def set_theme(theme_name, domain_name, runner): + command = BASE_OPENEDX_COMMAND + command += """ +echo "Assigning theme {theme_name} to {domain_name}..." +./manage.py lms shell -c " +from django.contrib.sites.models import Site +site, _ = Site.objects.get_or_create(domain='{domain_name}') +if not site.name: + site.name = '{domain_name}' + site.save() +site.themes.all().delete() +site.themes.create(theme_dir_name='{theme_name}')" +""" + command = command.format(theme_name=theme_name, domain_name=domain_name) + runner.check_service_is_activated("lms") + runner.exec("lms", command) diff --git a/tutor/templates/apps/openedx/config/cms.env.json b/tutor/templates/apps/openedx/config/cms.env.json index 4ae02e3..15295a8 100644 --- a/tutor/templates/apps/openedx/config/cms.env.json +++ b/tutor/templates/apps/openedx/config/cms.env.json @@ -23,7 +23,7 @@ "CELERY_BROKER_HOSTNAME": "{{ RABBITMQ_HOST }}", "CELERY_BROKER_USER": "{{ RABBITMQ_USERNAME }}", "CELERY_BROKER_PASSWORD": "{{ RABBITMQ_PASSWORD }}", - "COMPREHENSIVE_THEME_DIRS": ["/openedx/themes"], + "COMPREHENSIVE_THEME_DIRS": ["/openedx/themes", "/openedx/edx-platform/themes"], "STATIC_ROOT_BASE": "/openedx/staticfiles", "ELASTIC_SEARCH_CONFIG": [{ {% if ELASTICSEARCH_SCHEME == "https" %}"use_ssl": true,{% endif %} diff --git a/tutor/templates/apps/openedx/config/lms.env.json b/tutor/templates/apps/openedx/config/lms.env.json index 0c44495..09015c2 100644 --- a/tutor/templates/apps/openedx/config/lms.env.json +++ b/tutor/templates/apps/openedx/config/lms.env.json @@ -29,7 +29,7 @@ "CELERY_BROKER_PASSWORD": "{{ RABBITMQ_PASSWORD }}", "COMMENTS_SERVICE_URL": "http://{{ FORUM_HOST }}:4567", "COMMENTS_SERVICE_KEY": "forumapikey", - "COMPREHENSIVE_THEME_DIRS": ["/openedx/themes"], + "COMPREHENSIVE_THEME_DIRS": ["/openedx/themes", "/openedx/edx-platform/themes"], "STATIC_ROOT_BASE": "/openedx/staticfiles", "ELASTIC_SEARCH_CONFIG": [{ {% if ELASTICSEARCH_SCHEME == "https" %}"use_ssl": true,{% endif %}