Adding a Django App

The functionality of the Airavata Django Portal is broken up into separate Django apps. The apps live in the django_airavata/apps directory. When adding new functionality to the Django portal it may make sense to add it as a new separate Django app instead of adding it to an existing app. The following steps document how to do this.

Create the new Django App

For this example, assume the name of the app is myapp. The following also assumes you have sourced your virtual environment.

cd airavata-django-portal
mkdir django_airavata/apps/myapp
python manage.py startapp myapp django_airavata/apps/myapp

Integrating with the Django Portal

AppConfig settings

Edit the AppConfig so that it extends the AiravataAppConfig and fill in the required details:

from django_airavata.app_config import AiravataAppConfig


class MyAppConfig(AiravataAppConfig):
    name = 'django_airavata.apps.myapp'
    label = 'django_airavata_myapp'
    verbose_name = 'My App'
    app_order = 10
    url_home = 'django_airavata_myapp:home'
    fa_icon_class = 'fa-bolt'
    app_description = """
        My app for doing stuff in the Airavata Django Portal.
    """
    nav = [
        {
            'label': 'Dashboard',
            'icon': 'fa fa-tachometer-alt',
            'url': 'django_airavata_myapp:dashboard',
            'active_prefixes': ['dashboard']
        },
        # ... additional entries as needed
    ]

Some of these are self explanatory, but here are some details on each of these properties:

  • name - this is the python package of the app
  • label - this needs to be unique across all installed Django apps. I just make this match the app_name in urls.py.
  • verbose_name - display name of app
  • app_order - order of app in the menu listing. Range is 0 - 100. See the other Django apps for their values to figure out how to order this app relative to them.
  • url_home - namespaced url of the "home" page of this app. This will be the url used when a user selects this app in a navigational menu.
  • fa_icon_class - a FontAwesome icon class. See the list of available icons for v. 4.7.
  • app_description - description of this app
  • nav - optional provide navigation into sections of the app. The nav is optional but is necessary to provide users with a link from the left hand side navigation bar to a url in your app.
    • label - textual label, displayed on hover in the side navigation bar
    • icon - FontAwesome icon, see fa_icon_class above
    • url - named or namespaced url
    • active_prefixes - list of strings that come after this app's base url for all urls that are considered "active" for this nav item. This is used to highlight the currently active nav item in the left side navigation bar. For example, let's say the app's base url is "/myapp" and urls belonging to the "projects" nav item are of the form "/myapp/projects/<project_id>" and "/myapp/new-project". Then you would set active_prefixes to ["projects", "new-project"]. These strings can also be regular expressions.

Add AppConfig to INSTALLED_APPS

Edit INSTALLED_APPS in settings.py:

INSTALLED_APPS = [
  # ...
  'django_airavata.apps.myapp.apps.MyAppConfig'
]

Add Webpack bundle loader config to settings.py

If the new app has Webpack built frontend, then add the following configuration to WEBPACK_LOADER in settings.py:

...
'MYAPP': {
  'BUNDLE_DIR_NAME': 'django_airavata_myapp/dist/',
  'STATS_FILE': os.path.join(
      BASE_DIR,
      'django_airavata',
      'apps',
      'myapp',
      'static',
      'django_airavata_myapp',
      'dist',
      'webpack-stats.json'),
  'TIMEOUT': 60,
},
...

Add the apps urls to the site's urls.py

Edit django_airavata/urls.py and add the app's urls config:

urlpatterns = [
    re_path(r'^djadmin/', admin.site.urls),
    re_path(r'^admin/', include('django_airavata.apps.admin.urls')),
    re_path(r'^auth/', include('django_airavata.apps.auth.urls')),
    re_path(r'^workspace/', include('django_airavata.apps.workspace.urls')),
    re_path(r'^api/', include('django_airavata.apps.api.urls')),
    re_path(r'^groups/', include('django_airavata.apps.groups.urls')),
    re_path(r'^dataparsers/', include('django_airavata.apps.dataparsers.urls')),
    # ... Add the app urls here
    re_path(r'^myapp/', include('django_airavata.apps.myapp.urls')),
    # ...
    path('sdk/', include('airavata_django_portal_sdk.urls')),
    re_path(r'^home$', views.home, name='home'),
    re_path(r'^cms/', include(wagtailadmin_urls)),
    re_path(r'^documents/', include(wagtaildocs_urls)),
    # For testing, developing error pages
    re_path(r'^400/', views.error400),
    re_path(r'^403/', views.error403),
    re_path(r'^404/', views.error404),
    re_path(r'^500/', views.error500),
]

App urls.py and base template

Let's add a starter home page and urls.py config for this app. Create a urls.py file in myapp/:

from django.urls import path

from . import views

app_name = 'django_airavata_myapp'
urlpatterns = [
    path('home/', views.home, name='home'),
]

Add a view function called home in views.py:

from django.shortcuts import render


def home(request):
    return render(request, 'django_airavata_myapp/home.html')

Create a templates directory called in myapp called templates/django_airavata_myapp/.

Then create a base template in that directory called base.html. We'll create this file assuming that it will load webpack bundles generated by vue-cli:


{% extends 'base.html' %}

{% load static %}
{% load render_bundle from webpack_loader %}

{% block css %}
{% render_bundle 'chunk-vendors' 'css' 'MYAPP' %}
{% comment %}BUT NOTE: if you only have one entry point you won't have a 'chunk-common' bundle so you may need to comment out the next line until you have more than one entry point.{% endcomment %}
{% render_bundle 'chunk-common' 'css' 'MYAPP' %}
{% render_bundle bundle_name 'css' 'MYAPP' %}
{% endblock %}

{% block content %}
<div id="{{ bundle_name }}"/>
{% endblock %}


{% block scripts %}
{% render_bundle 'chunk-vendors' 'js' 'MYAPP' %}
{% comment %}BUT NOTE: if you only have one entry point you won't have a 'chunk-common' bundle so you may need to comment out the next line until you have more than one entry point.{% endcomment %}
{% render_bundle 'chunk-common' 'js' 'MYAPP' %}
{% render_bundle bundle_name 'js' 'MYAPP' %}
{% endblock %}

Now, create a home.html template:

{% extends './base.html' %}
{% load static %}
{% block css %}
{% comment %}This isn't a Vue.js app, so just turn off loading CSS.{% endcomment %}
{% endblock %}
{% block content %}

<h1>Hello World!</h1>

{% endblock content %}
{% block scripts %}
{% comment %}This isn't a Vue.js app, so just turn off loading JavaScript.{% endcomment %}
{% endblock %}

Now if you log into the Django portal you should see "My App" in the menu at the top and clicking on it should display the home page of this app.

JS build config - Vue.js

Now we'll add JavaScript build config to the app using Vue.js, npm and webpack.

Note

These instructions are likely to become outdated. You can also run vue create to create these configuration files. Also see the existing Django apps, like workspace, and compare how they are set up.

Add a package.json file to the app's directory (i.e., django_airavata/apps/myapp):

{
    "name": "django-airavata-myapp-views",
    "description": "A Vue.js project",
    "version": "1.0.0",
    "author": "Apache Airavata <dev@airavata.apache.org>",
    "private": true,
    "scripts": {
        "serve": "vue-cli-service serve",
        "build": "vue-cli-service build",
        "lint": "vue-cli-service lint",
        "format": "prettier --write ."
    },
    "dependencies": {
        "bootstrap": "^4.3.1",
        "bootstrap-vue": "^2.21.2",
        "core-js": "^3.8.3",
        "django-airavata-api": "link:../api",
        "django-airavata-common-ui": "link:../../static/common",
        "vue": "^2.5.21"
    },
    "devDependencies": {
        "@babel/core": "^7.12.16",
        "@babel/eslint-parser": "^7.12.16",
        "@vue/cli-plugin-babel": "~5.0.8",
        "@vue/cli-plugin-eslint": "~5.0.8",
        "@vue/cli-service": "~5.0.8",
        "eslint": "^7.32.0",
        "eslint-plugin-vue": "^8.0.3",
        "prettier": "^2.1.2",
        "vue-template-compiler": "^2.5.22",
        "webpack-bundle-tracker": "^0.4.2-beta"
    },
    "eslintConfig": {
        "root": true,
        "env": {
            "node": true
        },
        "extends": ["plugin:vue/essential", "eslint:recommended"],
        "rules": {},
        "parserOptions": {
            "parser": "@babel/eslint-parser",
            "requireConfigFile": false
        }
    },
    "postcss": {
        "plugins": {
            "autoprefixer": {}
        }
    },
    "browserslist": ["> 1%", "last 2 versions", "not dead"]
}

Run yarn which will install these dependencies and also create a yarn.lock file with locked dependency versions.

Add a babel.config.js to this directory too:

module.exports = {
    presets: ["@vue/cli-plugin-babel/preset"],
};

Now add a vue.config.js file too:

const BundleTracker = require("webpack-bundle-tracker");
const path = require("path");

module.exports = {
    publicPath:
        process.env.NODE_ENV === "development"
            ? "http://localhost:9000/static/django_airavata_myapp/dist/"
            : "/static/django_airavata_myapp/dist/",
    outputDir: "./static/django_airavata_myapp/dist",
    pages: {
        home: "./static/django_airavata_myapp/js/entry-home",
        // additional entry points go here ...
    },
    configureWebpack: {
        plugins: [
            new BundleTracker({
                filename: "webpack-stats.json",
                path: "./static/django_airavata_myapp/dist/",
            }),
        ],
    },
    devServer: {
        port: 9000,
        headers: {
            "Access-Control-Allow-Origin": "*",
        },
        hot: true,
    },
};

You'll customize pages by modifying and/or adding additional entry points and you'll need to modify publicPath and outputDir and the BundleTracker config to correspond to your folder structure.

Now create a static folder for holding javascript code. For this example we would create static/django_airavata_myapp/js. In this folder you can put the entry points, for example entry-home.js.

For each entry point you'll create a template, extending your app's base.html and including that entry points generated css and js file. See Adding an entry point for further instructions.

For a complete example, see the workspace app.

build_js.sh build script

In the root of the project is a master build script, build_js.sh, that generates a production build of all of the JS frontend code in the project. Add a line in there for your Django app, like so:

...
(cd $SCRIPT_DIR/django_airavata/apps/myapp && yarn && yarn run build) || exit 1

You can test it by running ./build_js.sh in the root folder.