====================== Adding Custom Web Apps ====================== To extend **e2xgrader** you can add custom server apps to it. A custom server app can add templates, handlers and change the tornado settings of the jupyter server. Different apps are activated in different modes. You can configure which apps should be activated in each mode via the `TeacherExtension`, `StudentExtension` and `StudentExamExtension` traits. Creating a Simple App --------------------- To create an app, you need to install `e2xcore` and extend the `BaseApp` class. As an example we will build a simple app that converts student score into letter grades. The layout of the app is: .. code-block:: sh :caption: Layout of the app grade_converter ├── pyproject.toml ├── README.md └── grade_converter ├── app.py ├── handlers.py └── templates ├── base.tpl └── convert_grades.tpl The Basic App ~~~~~~~~~~~~~ The app will display a page where you can convert a grade and respond with the letter grade. Let's start by looking at the file `app.py`. .. code-block:: python :caption: app.py # File app.py import os from e2xcore import BaseApp, urljoin from nbgrader.apps.baseapp import NbGrader from traitlets import Dict, Unicode, Float from traitlets.config import LoggingConfigurable from .handlers import default_handlers class GradeConverter(NbGrader, BaseApp): grading_scheme = Dict( key_trait=Unicode(), value_trait=Float(), default_value={ "A+": 0.97, "A": 0.93, "A-": 0.9, "B+": 0.87, "B": 0.83, "B-": 0.8, "C+": 0.77, "C": 0.73, "C-": 0.7, "D+": 0.67, "D": 0.65, "F": 0.0, }, ).tag(config=True) def __init__(self, **kwargs): NbGrader.__init__(self, **kwargs) BaseApp.__init__(self, **kwargs) template_path = os.path.join(os.path.dirname(__file__), "templates") def load_app(self): self.log.info("Loading the convert grades app") self.initialize([]) # Add the grading scheme to the tornado settings self.update_tornado_settings(dict(e2x_grading_scheme=self.grading_scheme)) self.add_handlers(default_handlers) self.add_template_path(self.template_path) The grading scheme can be configured via the `grading_scheme` trait. **e2xgrader** will call the `load_app` method when loading the app. Here we first start by putting the grading scheme in the tornado settings, making it accessible anywhere. Then we add the handlers defined in `handlers.py`. Finally we add the templates. The Handlers ~~~~~~~~~~~~ Let's define the handlers next. The file `handlers.py` looks like this: .. code-block:: python :caption: handlers.py # File handlers.py import json from e2xcore import urljoin from e2xcore.handlers import E2xApiHandler from tornado import web from nbgrader.server_extensions.formgrader.base import check_xsrf, BaseHandler # Helper function to convert the percentage to a grade given a grading scheme def convert_percentage_to_grade(percentage, grading_scheme): grade = "" for letter_grade, percent in sorted(grading_scheme.items(), key=lambda x: x[1]): if percentage < percent: break grade = letter_grade return grade # Handler to convert percentage to letter grade class ConvertGradeApiHandler(E2xApiHandler): def initialize(self): self.grading_scheme = self.settings["e2x_grading_scheme"] @web.authenticated def get(self): # Get the percentage argument and convert it to float percentage = float(self.get_argument("percentage")) # Respond with the converted grade self.finish( json.dumps(convert_percentage_to_grade(percentage, self.grading_scheme)) ) # Handler to display the template defined in convert_grades.tpl class ConvertGradeHandler(BaseHandler): @web.authenticated @check_xsrf def get(self): # Render the template, make sure to provide the base url here html = self.render("convert_grades.tpl", base_url=self.base_url) self.finish(html) # Urls under which the handlers are exposed default_handlers = [ (urljoin("e2x", "convert", "api"), ConvertGradeApiHandler), (urljoin("e2x", "convert", "app"), ConvertGradeHandler), ] Template ~~~~~~~~ If we want to create a link in the nbgrader formgrader sidebar, we have to change the base template ``base.tpl``. This template is located at ``e2xgrader/e2xgrader/server_extensions/apps/formgrader/templates/base.tpl``. We copy the template and change the lines concerning the sidebar: .. code-block:: jinja :caption: base.tpl ... {%- block sidebar -%}