How to write and integrate a custom plugin in tutor

Hello. I am new to tutor and openedx. I managed to fork and do customization on openedx and building my custom images. But I would like integrate my custom app(django+react) leveraging tutor plugins. I’ve checked the source codes for tutor plugins and edx plugins, and yet don’t have a clear idea.
Confusions -

  1. I see for any particular plugin (say notes), there’s a separate source code GitHub - openedx/edx-notes-api: edx-notes-api and a respective tutor plugin written GitHub - overhangio/tutor-notes: Student notes plugin for Tutor. Am I correct in understanding, that if I try to write my own plugin, I need to create a project using edx-cookiecutters/cookiecutter-django-app at master · openedx/edx-cookiecutters · GitHub and also to write a respective tutor plugin for that using GitHub - overhangio/cookiecutter-tutor-plugin: Cookiecutter for tutor plugins ?
  2. I’ve a public repo that hosts the django-app that I’ve created. Would it be sufficient to proceed further with that?
  3. I’ve gone through tutor docs that explains hooks and patches. What I understands hooks have options like filters and actions, depending on my needs, if I try to customize like project settings I can use filter and if I need to execute some callback I can do it with actions. Going through the source codes of existing tutor plugins, I found that patches are written for overriding specific template files or am have I incorrectly interpreted that? For prod deployments, which are the files I should override, any single example, if I need to run my custom django-app?
  4. I saw tutor docs(Plugin indexes — Tutor documentation) say, we can install a plugin enable disable that, for that we need to add my plugin to plugins index (3 options are there, but probably if I would like to add src indicating my git repo where I’ll write the tutor plugin). I can write that plugins.yml file on my computer(/path/to/my/local/index/plugins.yml), and add that yml to plugins index by running - tutor plugins index add /path/to/my/local/index. Now the problem here is my tutor aws image isn’t allowing me for options like - (plugins) index . What shall I do?
  5. If I need to interact with the core openedx db from my custom django-app, is it to be done using REST API only?
    Please correct me if I’m wrong. I’m not sure if I’m going towards the right direction

Yes, you need to create project and then write the plugin separately.
Here’s how you can decide what goes where:

  1. Project
    • All your functionality goes here.
  2. Plugin
    Think of this part like a bridge between your project and your openedx platform. Use this part to
    • Get whatever you might need from your platform to make your project work as a plugin.
    • Configure your platform to make your project to work as a plugin (I find it easier to use patches for this).

This may look complicated at first but this actually gives you freedom to use your project as a standalone app if you want to and it also allows you to use any project in any language to be used as a plugin.

Consider another repository for your plugin code.

Patch files are meant to add code to files in your platform whatever you put into a patch file will get appended at the bottom of it’s target file. A target file can be Dockerfile, docker-compose, env files, config.yml to name a few. You need to run tutor config save to apply the patches and adding code to these files is not recommended because all your changes will get overwritten if you run tutor local quickstart.

Template files are the files that you use to tell tutor how to setup your project

You can see Template patch catalog to see which patch file will make changes to which file in your platform.

One way is to follow these steps from Creating a Tutor plugin tutorial (I recommend you read this thoroughly):
(run tutor plugins printroot to see your root folder for plugins. Any python file in this folder will be treated as a plugin and you don’t even need to reindex)

  1. mkdir -p “$(tutor plugins printroot)”
  2. touch “$(tutor plugins printroot)/myplugin.py”
  3. tutor plugins list
  4. tutor plugins enable myplugin

Or, if you don’t want to go this way, check tutor version in your aws image. Plugin indexing was not there in versions <15.

I don’t have a definite answer to this but I think you can make direct db calls if you get the db configurations from your platform. I’ll post again if I find some more information on it.

I hope this was of some help.

2 Likes

Wow! That was really a thorough answer to my questions. Thanks a lot for your time. I’ll just give it a try. I understand the need of the apps as plugins as of now, it’s just that I’ll be writing it for the first time, so just wasn’t able to connect the dots.

Yes, I’ve used it in the past creating a directory and adding a myplugin.py at $(tutor plugins printroot) to override some specific config in production.py. But I was not that sure, how to do this when I’m trying to integrate a djangoapp and not just overriding some specific config param. I also noted, that when I wrote a plugin it was not immediately discovered to plugins list, but after some time it shows up. It seems some service is scanning the $(tutor plugins printroot) directory at a specific prescheduled interval.

I beg your pardon, I should have mentioned it at first, I’m using tutor 15.0.0, still I don’t see index option in there (available options - disable,enable,install, list, printroot). Then how would I write the .yaml and add it to index

Hey!
Hope you found your way around. Just in case you didn’t. I can only help you with the approach with myplugin.py since I haven’t tried out Tutor 15 myself yet.

I am writing some example code here, you can refer to that.
I’ll try to explain everything here but if you still need more clarification on any hook, you can refer to the Hooks Catalog.

Load Patches

Load all patches from the “tutor_plugin_root/myplugin/patches” folder. (Yes, the tutor won’t read patches on its own. You need to tell the tutor about them.)

for path in glob(
    os.path.join(pathlib.Path(__file__).parent, "myplugin", "patches", "*")
):
    with open(path, encoding="utf-8") as patch_file:
        hooks.Filters.ENV_PATCHES.add_item(
            (os.path.basename(path), patch_file.read()))

Add template folder

This tells tutor to look for templates in “tutor_plugin_root/myplugin/patches”. Much like django’s template folder

template_folder = os.path.join(os.path.dirname(__file__), "myplugin/templates")
hooks.Filters.ENV_TEMPLATE_ROOTS.add_item(template_folder)

Add template targets

Here you can map what path in your template root will be mapped to what path in your environment root.

hooks.Filters.ENV_TEMPLATE_TARGETS.add_item(
    ("myplugin/build", "plugins")
)

Add image(s) to build

Here you’ll tell the tutor about the Docker image that needs to be built for your plugin.
This one takes a list of images to be build with each one having these parameters:
<plugin_image_name>
<relative_path_to_image> (with respect to your environment root, we declared this in Add template targets
<tag_for_plugin_image>
<args_to_be_passed_to_docker_build>

hooks.Filters.IMAGES_BUILD.add_item(
    (
        "myplugin",
        ("plugins", "myplugin", "build", "myplugin"), 
        "myplugin:latest",
        (),
        )
)

Configurations

If you don’t want to add configurations with patches that can add code to your setting files directly. (as easy as it is, it still feels weird to add code to a file without actually having it open in your IDE).
I think you must be familiar with these.

hooks.Filters.CONFIG_DEFAULTS.add_items(
    [("key1", "value1"), ("key2","value2"),("key3","value3")]
)
hooks.Filters.CONFIG_UNIQUE.add_items(
    [("key1", "value1"), ("key2","value2"),("key3","value3")]
)
hooks.Filters.CONFIG_OVERRIDES.add_items(
    [("key1", "value1"), ("key2","value2"),("key3","value3")]
)
1 Like

@sagar I’ll just try it and will let you know here if I can make it work. Thank you so much for your kind support

1 Like