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.
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
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. """
Some of these are self explanatory, but here are some details on each of these properties:
urls.py
.Edit INSTALLED_APPS in settings.py:
INSTALLED_APPS = [ # ... 'django_airavata.apps.myapp.MyAppConfig' ]
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'), }, ...
Edit django_airavata/urls.py
and add the app's urls config:
urlpatterns = [ url(r'^djadmin/', admin.site.urls), url(r'^admin/', include('django_airavata.apps.admin.urls')), url(r'^auth/', include('django_airavata.apps.auth.urls')), url(r'^workspace/', include('django_airavata.apps.workspace.urls')), url(r'^api/', include('django_airavata.apps.api.urls')), url(r'^groups/', include('django_airavata.apps.groups.urls')), # ... Add the app urls here url(r'^myapp/', include('django_airavata.apps.myapp.urls')), # ... url(r'^home$', views.home, name='home'), url(r'^cms/', include(wagtailadmin_urls)), url(r'^documents/', include(wagtaildocs_urls)), url(r'', include(wagtail_urls)), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
It's important that the app urls are added before the wagtail_urls
since wagtail controls those urls.
Let's add a starter home page and urls.py config for this app. Create a urls.py
file in myapp/
:
from django.conf.urls import url from . import views app_name = 'django_airavata_myapp' urlpatterns = [ url(r'^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 nav-items %} {% comment %}Define the left side navigation for drilling into sub pages of your app{% endcomment %} <a href="{% url 'django_airavata_myapp:function1' %}" class="c-nav__item {% if request.active_nav_item == 'function1' %}is-active{% endif %}" data-toggle=tooltip data-placement=right title="Function #1"> <i class="fa fa-bolt"></i> <span class=sr-only>Function #1</span> </a> <a href="{% url 'django_airavata_myapp:function2' %}" class="c-nav__item {% if request.active_nav_item == 'function2' %}is-active{% endif %}" data-toggle=tooltip data-placement=right title="Function #2"> <i class="fa fa-bolt"></i> <span class=sr-only>Function #2</span> </a> <a href="{% url 'django_airavata_myapp:function3' %}" class="c-nav__item {% if request.active_nav_item == 'function3' %}is-active{% endif %}" data-toggle=tooltip data-placement=right title="Function #3"> <i class="fa fa-bolt"></i> <span class=sr-only>Function #3</span> </a> {% 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.
Now we'll add JavaScript build config to the app using Vue.js, npm and webpack.
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": "Marcus Christie <machristie@apache.org>", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "bootstrap": "^4.0.0-beta.2", "bootstrap-vue": "2.0.0-rc.26", "django-airavata-api": "file:../api", "django-airavata-common-ui": "file:../../static/common", "vue": "^2.5.21" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.1.1", "@vue/cli-plugin-eslint": "^3.1.1", "@vue/cli-service": "^3.1.1", "babel-eslint": "^10.0.1", "eslint": "^5.8.0", "eslint-plugin-vue": "^5.0.0-0", "vue-template-compiler": "^2.5.21", "webpack-bundle-tracker": "^0.4.2-beta" }, "eslintConfig": { "root": true, "env": { "node": true }, "extends": ["plugin:vue/essential", "eslint:recommended"], "rules": {}, "parserOptions": { "parser": "babel-eslint" } }, "postcss": { "plugins": { "autoprefixer": {} } }, "browserslist": ["> 1%", "last 2 versions", "not ie <= 8"] }
Add a babel.config.js
to this directory too:
module.exports = { presets: ["@vue/app"] };
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 ... }, css: { loaderOptions: { postcss: { config: { path: __dirname } } } }, configureWebpack: { plugins: [ new BundleTracker({ filename: "webpack-stats.json", path: "./static/django_airavata_myapp/dist/" }) ], optimization: { /* * Force creating a vendor bundle so we can load the 'app' and 'vendor' * bundles on development as well as production using django-webpack-loader. * Otherwise there is no vendor bundle on development and we would need * some template logic to skip trying to load it. * See also: https://bitbucket.org/calidae/dejavu/src/d63d10b0030a951c3cafa6b574dad25b3bef3fe9/%7B%7Bcookiecutter.project_slug%7D%7D/frontend/vue.config.js?at=master&fileviewer=file-view-default#vue.config.js-27 */ splitChunks: { cacheGroups: { vendors: { name: "chunk-vendors", test: /[\\/]node_modules[\\/]/, priority: -10, chunks: "initial" }, common: { name: "chunk-common", minChunks: 2, priority: -20, chunks: "initial", reuseExistingChunk: true } } } } }, chainWebpack: config => { /* * Specify the eslint config file otherwise it complains of a missing * config file for the ../api and ../../static/common packages * * See: https://github.com/vuejs/vue-cli/issues/2539#issuecomment-422295246 */ config.module .rule("eslint") .use("eslint-loader") .tap(options => { options.configFile = path.resolve(__dirname, "package.json"); return options; }); }, devServer: { port: 9000, headers: { "Access-Control-Allow-Origin": "*" }, hot: true, hotOnly: 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.
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 && npm install && npm run build) || exit 1
You can test it by running ./build_js.sh
in the root folder.