You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2021/05/20 17:15:51 UTC

[airavata-django-portal] branch airavata-3453 updated (2ca4a78 -> 0b4b95f)

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a change to branch airavata-3453
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git.


    from 2ca4a78  AIRAVATA-3453 Add icons
     new 181fd5d  AIRAVATA-3453 Calling context processor to populate species list
     new 0b4b95f  AIRAVATA-3453 Database model for registering custom application templates

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 django_airavata/app_config.py                      |   3 +-
 django_airavata/apps/api/admin.py                  |  24 +
 ...template_applicationtemplatecontextprocessor.py |  39 ++
 django_airavata/apps/api/models.py                 |  26 ++
 .../jquery.textcomplete.css                        |  33 --
 .../jquery.textcomplete.min.js                     |   3 -
 .../django_airavata_workspace/supcrtbl2.html       | 485 ---------------------
 django_airavata/apps/workspace/views.py            |  50 ++-
 8 files changed, 131 insertions(+), 532 deletions(-)
 create mode 100644 django_airavata/apps/api/migrations/0006_applicationtemplate_applicationtemplatecontextprocessor.py
 delete mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/jquery.textcomplete.css
 delete mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/jquery.textcomplete.min.js
 delete mode 100644 django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html

[airavata-django-portal] 01/02: AIRAVATA-3453 Calling context processor to populate species list

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch airavata-3453
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 181fd5d1884692092166d8c5347d68781fd0cc1d
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue May 18 16:54:42 2021 -0400

    AIRAVATA-3453 Calling context processor to populate species list
---
 .../workspace/templates/django_airavata_workspace/supcrtbl2.html  | 4 +++-
 django_airavata/apps/workspace/views.py                           | 8 ++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html b/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html
index 5edd42c..945026f 100644
--- a/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html
+++ b/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html
@@ -213,7 +213,9 @@ $('document').ready(function() {
   $('#reaction').textcomplete([{
     match: /(^|\b)(\S{1,})$/,
     search: function (term, callback) {
-      var words = ["ALMANDINE","ANDRADITE","GROSSULAR","KNORRINGITE","MAJORITE","PYROPE","SPESSARTINE","CLINOHUMITE","FAYALITE","FORSTERITE","MONTICELLITE","TEPHROITE","ANDALUSITE","KYANITE","Al-MULLITE","Si-MULLITE","Fe-CHLORITOID","Mg-CHLORITOID","Mn-CHLORITOID","Fe-STAUROLITE","Mg-STAUROLITE","Mn-STAUROLITE","HYDROXY-TOPAZ","AKERMANITE","JULGOLDITE(FeFe)","MERWINITE","PUMPELLYITE(FeAl)","PUMPELLYITE(MgAl)","RANKINITE","SPURRITE","TILLEYITE","ZIRCON","CLINOZOISITE","EPIDOTE(ORDERED)"," [...]
+      var words = [
+      {% for a_species in species %}"{{ a_species|escapejs }}", {% endfor %}
+      ];
       callback($.map(words, function (word) {
         return word.toLowerCase().indexOf(term.toLowerCase()) === 0 ? word : null;
       }));
diff --git a/django_airavata/apps/workspace/views.py b/django_airavata/apps/workspace/views.py
index a701800..0b69ede 100644
--- a/django_airavata/apps/workspace/views.py
+++ b/django_airavata/apps/workspace/views.py
@@ -69,6 +69,11 @@ def edit_project(request, project_id):
     })
 
 
+def species_list(request):
+    return {
+        'species': ["ALMANDINE","ANDRADITE","GROSSULAR","KNORRINGITE","MAJORITE","PYROPE","SPESSARTINE","CLINOHUMITE","FAYALITE","FORSTERITE","MONTICELLITE","TEPHROITE","ANDALUSITE","KYANITE","Al-MULLITE","Si-MULLITE","Fe-CHLORITOID","Mg-CHLORITOID","Mn-CHLORITOID","Fe-STAUROLITE","Mg-STAUROLITE","Mn-STAUROLITE","HYDROXY-TOPAZ","AKERMANITE","JULGOLDITE(FeFe)","MERWINITE","PUMPELLYITE(FeAl)","PUMPELLYITE(MgAl)","RANKINITE","SPURRITE","TILLEYITE","ZIRCON","CLINOZOISITE","EPIDOTE(ORDERED)", [...]
+    }
+
 @login_required
 def create_experiment(request, app_module_id):
     request.active_nav_item = 'dashboard'
@@ -113,6 +118,9 @@ def create_experiment(request, app_module_id):
     if 'experiment-data-dir' in request.GET:
         context['experiment_data_dir'] = request.GET['experiment-data-dir']
 
+    # Run through context processors
+    for processor in [species_list]:
+        context.update(species_list(request))
     return render(request,
                   #   'django_airavata_workspace/create_experiment.html',
                   'django_airavata_workspace/supcrtbl2.html',

[airavata-django-portal] 02/02: AIRAVATA-3453 Database model for registering custom application templates

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch airavata-3453
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 0b4b95f60b9ed4247d14915a3998feeb48663b10
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu May 20 13:15:39 2021 -0400

    AIRAVATA-3453 Database model for registering custom application templates
---
 django_airavata/app_config.py                      |   3 +-
 django_airavata/apps/api/admin.py                  |  24 +
 ...template_applicationtemplatecontextprocessor.py |  39 ++
 django_airavata/apps/api/models.py                 |  26 ++
 .../jquery.textcomplete.css                        |  33 --
 .../jquery.textcomplete.min.js                     |   3 -
 .../django_airavata_workspace/supcrtbl2.html       | 487 ---------------------
 django_airavata/apps/workspace/views.py            |  58 ++-
 8 files changed, 131 insertions(+), 542 deletions(-)

diff --git a/django_airavata/app_config.py b/django_airavata/app_config.py
index f747b15..f34195d 100644
--- a/django_airavata/app_config.py
+++ b/django_airavata/app_config.py
@@ -76,8 +76,7 @@ def get_default_url_home(app_config):
             first_named_url = urlpattern.name
             break
     if not first_named_url:
-        raise Exception("{} has no named urls, "
-                        "can't figure out default home URL")
+        raise Exception(f"{urls} has no named urls, can't figure out default home URL")
     if app_name:
         return app_name + ":" + first_named_url
     else:
diff --git a/django_airavata/apps/api/admin.py b/django_airavata/apps/api/admin.py
index b97a94f..a5d0364 100644
--- a/django_airavata/apps/api/admin.py
+++ b/django_airavata/apps/api/admin.py
@@ -1,2 +1,26 @@
 
 # Register your models here.
+
+from django.contrib import admin
+
+from .models import ApplicationTemplate, ApplicationTemplateContextProcessor
+
+
+class ApplicationTemplateContextProcessorInline(admin.StackedInline):
+    model = ApplicationTemplateContextProcessor
+    extra = 1
+
+
+class ApplicationTemplateAdmin(admin.ModelAdmin):
+    fields = ['application_module_id', 'template_path']
+    list_display = ['application_module_id', 'template_path', 'updated_by', 'updated']
+    inlines = [ApplicationTemplateContextProcessorInline]
+
+    def save_model(self, request, obj, form, change):
+        obj.updated_by = request.user
+        if not obj.pk:
+            obj.created_by = request.user
+        return super().save_model(request, obj, form, change)
+
+
+admin.site.register(ApplicationTemplate, ApplicationTemplateAdmin)
diff --git a/django_airavata/apps/api/migrations/0006_applicationtemplate_applicationtemplatecontextprocessor.py b/django_airavata/apps/api/migrations/0006_applicationtemplate_applicationtemplatecontextprocessor.py
new file mode 100644
index 0000000..688cc63
--- /dev/null
+++ b/django_airavata/apps/api/migrations/0006_applicationtemplate_applicationtemplatecontextprocessor.py
@@ -0,0 +1,39 @@
+# Generated by Django 2.2.17 on 2021-05-20 17:13
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('django_airavata_api', '0005_delete_user_files'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ApplicationTemplate',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('application_module_id', models.CharField(max_length=255, unique=True)),
+                ('template_path', models.TextField()),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('updated', models.DateTimeField(auto_now=True)),
+                ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)),
+                ('updated_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='ApplicationTemplateContextProcessor',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('callable_path', models.CharField(max_length=255)),
+                ('application_template', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='context_processors', to='django_airavata_api.ApplicationTemplate')),
+            ],
+            options={
+                'unique_together': {('application_template', 'callable_path')},
+            },
+        ),
+    ]
diff --git a/django_airavata/apps/api/models.py b/django_airavata/apps/api/models.py
index 3826d9e..b6b7e27 100644
--- a/django_airavata/apps/api/models.py
+++ b/django_airavata/apps/api/models.py
@@ -1,3 +1,4 @@
+from django.conf import settings
 from django.db import models
 
 
@@ -33,3 +34,28 @@ class User_Notifications(models.Model):
     username = models.CharField(max_length=64)
     notification_id = models.CharField(max_length=255)
     is_read = models.BooleanField(default=False)
+
+
+class ApplicationTemplate(models.Model):
+    application_module_id = models.CharField(max_length=255, unique=True)
+    template_path = models.TextField()
+    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="+")
+    updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="+")
+    created = models.DateTimeField(auto_now_add=True)
+    updated = models.DateTimeField(auto_now=True)
+
+    def __str__(self):
+        return f"{self.application_module_id}: {self.template_path}"
+
+
+class ApplicationTemplateContextProcessor(models.Model):
+    application_template = models.ForeignKey(ApplicationTemplate, on_delete=models.CASCADE, related_name="context_processors")
+    # Use django.util.module_loading.import_string to import
+    # https://docs.djangoproject.com/en/3.2/ref/utils/#module-django.utils.module_loading
+    callable_path = models.CharField(max_length=255)
+
+    class Meta:
+        unique_together = (('application_template', 'callable_path'),)
+
+    def __str__(self):
+        return self.callable_path
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/jquery.textcomplete.css b/django_airavata/apps/workspace/static/django_airavata_workspace/jquery.textcomplete.css
deleted file mode 100644
index 37a761b..0000000
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/jquery.textcomplete.css
+++ /dev/null
@@ -1,33 +0,0 @@
-/* Sample */
-
-.dropdown-menu {
-    border: 1px solid #ddd;
-    background-color: white;
-}
-
-.dropdown-menu li {
-    border-top: 1px solid #ddd;
-    padding: 2px 5px;
-}
-
-.dropdown-menu li:first-child {
-    border-top: none;
-}
-
-.dropdown-menu li:hover,
-.dropdown-menu .active {
-    background-color: rgb(110, 183, 219);
-}
-
-
-/* SHOULD not modify */
-
-.dropdown-menu {
-    list-style: none;
-    padding: 0;
-    margin: 0;
-}
-
-.dropdown-menu a:hover {
-    cursor: pointer;
-}
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/jquery.textcomplete.min.js b/django_airavata/apps/workspace/static/django_airavata_workspace/jquery.textcomplete.min.js
deleted file mode 100644
index ce1536d..0000000
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/jquery.textcomplete.min.js
+++ /dev/null
@@ -1,3 +0,0 @@
-/*! jquery-textcomplete - v1.8.4 - 2017-08-29 */
-!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"==typeof module&&module.exports){var b=require("jquery");module.exports=a(b)}else a(jQuery)}(function(a){if("undefined"==typeof a)throw new Error("jQuery.textcomplete requires jQuery");return+function(a){"use strict";var b=function(a){console.warn&&console.warn(a)},c=1;a.fn.textcomplete=function(d,e){var f=Array.prototype.slice.call(arguments);return this.each(function(){var g=this,h=a(this),i=h.da [...]
-//# sourceMappingURL=dist/jquery.textcomplete.min.map
\ No newline at end of file
diff --git a/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html b/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html
deleted file mode 100644
index 945026f..0000000
--- a/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html
+++ /dev/null
@@ -1,487 +0,0 @@
-{% extends 'base.html' %}
-
-{% load static %}
-{% load render_bundle from webpack_loader %}
-
-{% block css %}
-{% comment %} TODO: are chunk-vendors and chunk-common needed? {% endcomment %}
-{% comment %} {% render_bundle 'chunk-vendors' 'css' 'WORKSPACE' %} {% endcomment %}
-{% comment %} {% render_bundle 'chunk-common' 'css' 'WORKSPACE' %} {% endcomment %}
-{% comment %} {% render_bundle 'adpf' 'css' 'WORKSPACE' %} {% endcomment %}
-	<link href="{% static 'django_airavata_workspace/jquery.textcomplete.css' %}" rel="stylesheet" type="text/css">
-{% endblock %}
-
-{% block content %}
-{% comment %} TODO: maybe use wc- as a prefix? {% endcomment %}
-<div class="main-content-wrapper">
-    <main class="main-content">
-        <div class="container-fluid">
-          <h1>SUPCRTBL Online Version 1.0.1</h2>
-          <adpf-experiment-editor
-            id="experiment-editor"
-            application-id="supcrtbl_eb4216b3-fcbf-4422-a70d-27af2550cfb6"
-            {% if experiment_id %}
-            experiment-id="{{ experiment_id }}"
-            {% endif %} >
-            <div id="solvent" slot="Specify Solvent Phase Region">
-              <label for="solventPhase">Specify solvent phase region:</label>
-              <input type="radio" name="solventPhase" value="0">One-phase region</input> <br/>
-              <input type="radio" name="solventPhase" value="1">liquid vapor saturation curve</input>
-            </div>
-            <div id="lipVapSat" slot="Specify Independent Liq-vap Saturation Variable (3)">
-              <label for="lipVapSatVar">Specify independent liq-vap saturation variable:</label>
-              <input type="radio" name="lipVapSatVar" value="0">Temperature (degCel)</input> <br/>
-              <input type="radio" name="lipVapSatVar" value="1">Pressure (bars)</input>
-            </div>
-            <div id="indVar" slot="Specify Independent State Variables">
-              <label for="independentStateVar">Specify independent State Variables:</label>
-              <input type="radio" name="independentStateVar" value="0" >Temperature (degCel), density(H2O) (g/cc)</input> <br/>
-              <input type="radio" name="independentStateVar" value="1">Temperature (degCel), pressure (bars)</input>
-            </div>
-            <div id="univariantCurve" slot="Univariant Curve Option (2)">
-              <label for="univariantCurveOption">Would you like to use the univariant curve option?
-              (i.e., calculate T(logK,P) or P(logK,T):</label>
-              <input type="radio" name="univariantCurveOption" value="0">Yes</input> <br/>
-              <input type="radio" name="univariantCurveOption" value="1">No</input>
-            </div>
-            <div id="tabulationBaric" slot="Specify Tabulation Option (2)">
-              <label for="tabulationBaricOption">Specify tabulation option:</label>
-              <input type="radio" name="tabulationBaricOption" value="0">Calculate ISOBARIC (T) tables</input> <br/>
-              <input type="radio" name="tabulationBaricOption" value="1">Calculate ISOTHERMAL (D) tables</input>
-            </div>
-            <div id="tabulationChoric" slot="Specify Tabulation Option (1)">
-              <label for="tabulatioChoricnOption">Specify tabulation option:</label>
-              <input type="radio" name="tabulationChoricOption" value="0">Calculate ISOCHORIC (T) tables</input> <br/>
-              <input type="radio" name="tabulationChoricOption" value="1">Calculate ISOTHERMAL (D) tables</input>
-            </div>
-            <!-- TODO: Specify Table-increment Option (2) ? -->
-            <!-- TODO: Specify Table-increment Option (3) ?-->
-            <div id="table" slot="Specify Table Increment Option (1)">
-              <label for="tableIncrement">Specify table-increment option:</label>
-              <input type="radio" name="tableIncrement" value="0">Calculate tables having uniform increments</input> <br/>
-              <input type="radio" name="tableIncrement" value="1">Calculate tables having unequal increments</input>
-            </div>
-            <div id="univariantCalc" slot="Specify Univariant Calculation Option (2)">
-              <label for="univariantCalcOption">Specify univariant calculation option:</label>
-              <input type="radio" name="univariantCalcOption" value="0">Calculate T (logK, isobars)</input> <br/>
-              <input type="radio" name="univariantCalcOption" value="1">Calculate P (logK, isotherms)</input>
-            </div>
-            <div id="isochores" slot="Specify ISOCHORES (g/cc) Range (1)">
-              <label for="isochoresRange">Specify ISOCHORES(g/cc) range: <br/>
-                min, max, increment</label>
-              <input type="text" name="isochoresRange" id="isochoresRange" />
-            </div>
-            <!-- TODO: Specify TEMP(degCel) Range (2) ? -->
-            <!-- TODO: Specify TEMP(degCel) Range (3) ? -->
-            <div id="temp" slot="Specify TEMP(degCel) Range (1)">
-              <label for="tempRange">Specify TEMP (degCel) range: <br/>
-                min, max, increment</label>
-              <input type="text" name="tempRange" id="tempRange" />
-            </div>
-            <div id="dH2OTemp" slot="Specify DH2O(g/cc) TEMP(degCel) ValuePairs (1)">
-              <label for="dH2OTempPairs">Specify DH2O(g/cc), TEMP (degCel) value pairs: <br/>
-                One pair per line, ending with 0,0</label>
-              <textarea name="dH2OTempPairs" id="dH2OTempPairs" ></textarea>
-            </div>
-            <!-- TODO: or "Specify ISOTHERMS(degCel) Range (2)" ?-->
-            <div id="isotherms" slot="Specify ISOTHERMS(degCel) range (1)">
-              <label for="isothermsRange">Specify ISOTHERMS (degCel) range: <br/>
-                min, max, increment</label>
-              <input type="text" name="isothermsRange" id="isothermsRange" />
-            </div>
-            <div id="dH2O" slot="Specify DH2O(g/cc) Range (1)">
-              <label for="dH2ORange">Specify DH2O (g/cc) range: <br/>
-                min, max, increment</label>
-              <input type="text" name="dH2ORange" id="dH2ORange" />
-            </div>
-            <div id="tempDH2O" slot="Specify TEMP(degCel) DH2O(g/cc) Value Pairs (1)">
-              <label for="tempDH2OPairs">Specify TEMP (degCel), DH2O(g/cc) value pairs: <br/>
-                One pair per line, ending with 0,0</label>
-              <textarea name="tempDH2OPairs" id="tempDH2OPairs" ></textarea>
-            </div>
-            <div id="isobars" slot="Specify ISOBARS(bars) Range (2)">
-              <label for="isobarsRange">Specify ISOBARS(bars) range: <br/>
-                min, max, increment</label>
-              <input type="text" name="isobarsRange" id="isobarsRange" />
-            </div>
-            <div id="logK" slot="Specify LogK Range (2)">
-              <label for="logKRange">Specify logK range: <br/>
-                Kmin, Kmax, Kincrement</label>
-              <input type="text" name="logKRange" id="logKRange" />
-            </div>
-            <div id="logKBoundingTemp" slot="Specify Bounding TEMP(degCel) Range (2)">
-              <label for="logKBoundingTempRange">Specify bounding TEMP (degCel) range: <br/>
-              T min, T max:</label>
-              <input type="text" name="logKBoundingTempRange" id="logKBoundingTempRange" />
-            </div>
-            <div id="logKBoundingPres" slot="Specify Bounding PRES(bars) Range (2)">
-              <label for="logKBoundingPresRange">Specify bounding PRES (bars) range: <br/>
-              P min, P max:</label>
-              <input type="text" name="logKBoundingPresRange" id="logKBoundingPresRange" />
-            </div>
-            <!-- TODO: what input does this match? -->
-            <div id="presTemp">
-              <label for="presTempPairs">Specify PRES (bars), TEMP (degCel) value pairs: <br/>
-                One pair per line, ending with 0,0</label>
-              <textarea name="presTempPairs" id="presTempPairs"></textarea>
-            </div>
-            <!-- TODO: or Specify PRES(bars) range (2) ?-->
-            <div id="pres" slot="Specify PRES(bars) Range (3)">
-              <label for="presRange">Specify PRES (bars) range: <br/>
-                min, max, increment</label>
-              <input type="text" name="presRange" id="presRange" />
-            </div>
-            <div id="tempPres" slot="Specify TEMP(degCel) Pres(g/cc) Value Pairs (2)">
-              <label for="tempPresPairs">Specify TEMP (degCel), Pres(g/cc) value pairs: <br/>
-                One pair per line, ending with 0,0</label>
-              <textarea name="tempPresPairs" id="tempPresPairs" ></textarea>
-            </div>
-            <div id="lipVapSatTemp" slot="Specify Liq-vap Saturation TEMP(degCel) Values (3)">
-              <label for="lipVapSatTempVal">Specify liq-vap saturation TEMP (degCel) values: <br/>
-                One per line, concluding with 0</label>
-              <textarea name="lipVapSatTempVal" id="lipVapSatTempVal" ></textarea>
-            </div>
-            <div id="lipVapSatPres" slot="Specify Liq-vap Saturation PRES(bars) Values (3)">
-              <label for="lipVapSatPresVal">Specify liq-vap saturation PRES (bars) values: <br/>
-                One per line, concluding with 0</label>
-              <textarea name="lipVapSatPresVal" id="lipVapSatPresVal" ></textarea>
-            </div>
-            <br/><br/>
-            <div id="lipVapSatPres" slot="Reaction">
-              <label for="reaction">Insert reactions here, 1 species per line, empty line between reactions  <br/>
-              Numbers are the stoichiometric coefficient of the species. <br/>
-              Positive numbers are products and negative numbers are reactants, e.g. <br/>
-              QUARTZ => SiO2,aq: <br/>
-              <code>
-              -1 QUARTZ <br/>
-              1 SiO2,aq
-              </code>
-              </label>
-              <textarea rows="15" name="reaction" id="reaction" required style="width:200px;"></textarea>
-            </div>
-            <br/><br/>
-            <!-- TODO: what input does this match? -->
-            <div id="kalFormat">
-              <label for="kalFormatOption">Specify option for x-y plot files:</label>
-              <input type="radio" name="kalFormatOption" value="0" required>Do not generate plot files</input> <br/>
-              <input type="radio" name="kalFormatOption" value="1">Generate plot files in generic format</input>
-            </div>
-            <br/><br/>
-          </adpf-experiment-editor>
-        </div>
-    </main>
-</div>
-{% endblock content %}
-
-{% block scripts %}
-{% comment %} {% render_bundle 'chunk-vendors' 'js' 'WORKSPACE' %} {% endcomment %}
-<script src="{% static 'django_airavata_workspace/wc/adpf.chunk-vendors.js' %}"></script>
-{% comment %} {% render_bundle 'chunk-common' 'js' 'WORKSPACE' %} {% endcomment %}
-{% comment %} {% render_bundle 'adpf' 'js' 'WORKSPACE' %} {% endcomment %}
-<script src="{% static 'django_airavata_workspace/wc/adpf.min.js' %}"></script>
-<script src="{% static 'django_airavata_workspace/jquery.textcomplete.min.js' %}"></script>
-<script>
-document.getElementById("experiment-editor").addEventListener('loaded', e => {
-  const [experiment] = e.detail;
-  for (const input of experiment.experimentInputs) {
-    const slotEl = document.querySelector(`[slot="${input.name}"]`);
-    // console.log("slotEl=", slotEl);
-    if (slotEl) {
-      const inputEls = slotEl.querySelectorAll('input');
-      const textareaEl = slotEl.querySelector('textarea');
-      // console.log("inputEls=", inputEls);
-      if (inputEls && inputEls.length > 0) {
-        if (inputEls[0].type === 'text') {
-          inputEls[0].value = input.value;
-        } else if (inputEls[0].type === 'radio') {
-          for (radio of inputEls) {
-            if (radio.value === input.value) {
-              radio.checked = true;
-              break;
-            }
-          }
-        }
-      } else if (textareaEl) {
-        textareaEl.value = input.value;
-      }
-    }
-  }
-  resetViews();
-});
-
-$('document').ready(function() {
-  $('#reaction').textcomplete([{
-    match: /(^|\b)(\S{1,})$/,
-    search: function (term, callback) {
-      var words = [
-      {% for a_species in species %}"{{ a_species|escapejs }}", {% endfor %}
-      ];
-      callback($.map(words, function (word) {
-        return word.toLowerCase().indexOf(term.toLowerCase()) === 0 ? word : null;
-      }));
-    },replace: function (word) {
-      return word + ' ';
-    }
-  }]).on('textComplete:select', e => {
-    // Keep the value of 'Reaction' up-to-date when an autocomplete option is selected.
-    // experiment-editor listens for 'input' events, which are triggered when user
-    // types in 'Reaction' textarea, but selecting an autocomplete option doesn't
-    // trigger an 'input' event, so we need to manually update the value of the
-    // experiment input when an autocomplete option is selected.
-    document.getElementById('experiment-editor').vueComponent.updateInputValue('Reaction', e.target.value);
-  });
-});
-
-function resetViews() {
-		$('#lipVapSat').hide();
-		$('#indVar').hide();
-		$('#tabulationBaric').hide();
-		$('#tabulationChoric').hide();
-		$('#table').hide();
-		$('#univariantCurve').hide();
-		$('#univariantCalc').hide();
-		$('#isochores').hide();
-		$('#temp').hide();
-		$('#dH2OTemp').hide();
-		$('#logK').hide();
-		$('#isotherms').hide();
-		$('#dH2O').hide();
-		$('#tempDH2O').hide();
-		$('#isobars').hide();
-		$('#logKBoundingTemp').hide();
-		$('#logKBoundingPres').hide();
-		$('#presTemp').hide();
-		$('#pres').hide();
-		$('#tempPres').hide();
-		$('#lipVapSatTemp').hide();
-		$('#lipVapSatPres').hide();
-		$('#submit').hide();
-		if($('input:radio[name=solventPhase]:checked').val() == "0"){
-			$('#indVar').show();
-			if($('input:radio[name=independentStateVar]:checked').val() == "0"){
-				$('#tabulationChoric').show();
-				if($('input:radio[name=tabulationChoricOption]:checked').val() == "0"){
-					$('#table').show();
-					if($('input:radio[name=tableIncrement]:checked').val() == "0"){
-						$('#isochores').show();
-						$('#temp').show();
-						$('#submit').show();
-
-					}
-					else if($('input:radio[name=tableIncrement]:checked').val() == "1"){
-						$('#dH2OTemp').show();
-						$('#submit').show();
-					}
-				}
-				else if($('input:radio[name=tabulationChoricOption]:checked').val() == "1"){
-					$('#table').show();
-					if($('input:radio[name=tableIncrement]:checked').val() == "0"){
-						$('#isotherms').show();
-						$('#dH2O').show();
-						$('#submit').show();
-
-					}
-					else if($('input:radio[name=tableIncrement]:checked').val() == "1"){
-						$('#tempDH2O').show();
-						$('#submit').show();
-					}
-				}
-			}
-			else if($('input:radio[name=independentStateVar]:checked').val() == "1"){
-				$('#univariantCurve').show();
-				if($('input:radio[name=univariantCurveOption]:checked').val() == "0"){
-					$('#univariantCalc').show();
-					if($('input:radio[name=univariantCalcOption]:checked').val() == "0") {
-							$('#isobars').show();
-							$('#logK').show();
-							$('#logKBoundingTemp').show();
-							$('#submit').show();
-					}
-					else if($('input:radio[name=univariantCalcOption]:checked').val() == "1"){
-							$('#isotherms').show();
-							$('#logK').show();
-							$('#logKBoundingPres').show();
-							$('#submit').show();
-					}
-				}
-				if($('input:radio[name=univariantCurveOption]:checked').val() == "1"){
-					$('#tabulationBaric').show();
-					if($('input:radio[name=tabulationBaricOption]:checked').val() == "0"){
-						$('#table').show();
-						if($('input:radio[name=tableIncrement]:checked').val() == "0"){
-							$('#isobars').show();
-							$('#temp').show();
-							$('#submit').show();
-
-						}
-						else if($('input:radio[name=tableIncrement]:checked').val() == "1"){
-							$('#presTemp').show();
-							$('#submit').show();
-						}
-					}
-					else if($('input:radio[name=tabulationBaricOption]:checked').val() == "1"){
-						$('#table').show();
-						if($('input:radio[name=tableIncrement]:checked').val() == "0"){
-							$('#isotherms').show();
-							$('#pres').show();
-							$('#submit').show();
-						}
-						else if($('input:radio[name=tableIncrement]:checked').val() == "1"){
-							$('#tempPres').show();
-							$('#submit').show();
-						}
-					}
-				}
-
-			}
-		}
-		else if($('input:radio[name=solventPhase]:checked').val() == "1"){
-			$('#lipVapSat').show();
-			if($('input:radio[name=lipVapSatVar]:checked').val() == "0"){
-				$('#table').show();
-				if($('input:radio[name=tableIncrement]:checked').val() == "0"){
-					$('#temp').show();
-					$('#submit').show();
-				}
-				if($('input:radio[name=tableIncrement]:checked').val() == "1"){
-					$('#lipVapSatTemp').show();
-					$('#submit').show();
-				}
-			}
-			if($('input:radio[name=lipVapSatVar]:checked').val() == "1"){
-				$('#table').show();
-				if($('input:radio[name=tableIncrement]:checked').val() == "0"){
-					$('#pres').show();
-					$('#submit').show();
-				}
-				if($('input:radio[name=tableIncrement]:checked').val() == "1"){
-					$('#lipVapSatPres').show();
-					$('#submit').show();
-				}
-			}
-		}
-	}
-	$(document).ready(function() {
-		resetViews();
-		$("input:radio").change(function () {
-			resetViews();
-		});
-	});
-
-  function validateExperiment(event) {
-    // const [experiment] = event.detail;
-    if (!checkOutput()) {
-      console.log('save: preventing default');
-      event.preventDefault();
-    }
-  }
-  document.getElementById('experiment-editor').addEventListener('save', validateExperiment);
-
-	function checkOutput(){
-		var filledIn = true;
-		var textArr = ["isochoresRange","tempRange","isothermsRange","dH2ORange","isobarsRange","logKRange","presRange","logKBoundingTempRange","logKBoundingPresRange"];
-		for(let s of textArr){
-			if($("input#"+s).is(":visible")){
-				var iv = document.getElementById(s).value;
-				var ivs = iv.trim().split(" ");
-				var cvs = iv.trim().split(",");
-				var ccvs = iv.trim().split(", ");
-				var spaceSeperated = false;
-				var commaSeperated = false;
-				var commaSpaceSeperated = false;
-				if(s==="logKBoundingPresRange" || s==="logKBoundingTempRange"){
-					if(ccvs.length==2){
-						commaSpaceSeperated=true;
-						if(parseInt(ccvs[0],10)>parseInt(ccvs[1],10) || parseInt(ccvs[0],10) < 0 || parseInt(ccvs[1],10) < 0){alert("invalid values for "+s); return false;}
-					}
-					if(ivs.length==2 && !commaSpaceSeperated){
-						spaceSeperated=true;
-						if(parseInt(ivs[0],10)>parseInt(ivs[1],10) || parseInt(ivs[0],10) < 0 || parseInt(ivs[1],10) < 0){alert("invalid values for "+s); return false;}
-					}
-					if(cvs.length==2 && !commaSpaceSeperated){
-						commaSeperated=true;
-						if(parseInt(cvs[0],10)>parseInt(cvs[1],10) || parseInt(cvs[0],10) < 0 || parseInt(cvs[1],10) < 0){alert("invalid values for "+s); return false;}
-					}
-				}
-				else{
-					if(ccvs.length==3){
-						commaSpaceSeperated=true;
-						if(parseInt(ccvs[0],10)>parseInt(ccvs[1],10) || parseInt(ccvs[0],10) < 0 || parseInt(ccvs[1],10) < 0){alert("invalid values for "+s); return false;}
-					}
-					if(ivs.length==3 && !commaSpaceSeperated){
-						spaceSeperated=true;
-						if(parseInt(ivs[0],10)>parseInt(ivs[1],10) || parseInt(ivs[0],10) < 0 || parseInt(ivs[1],10) < 0){alert("invalid values for "+s); return false;}
-					}
-					if(cvs.length==3 && !commaSpaceSeperated){
-						commaSeperated=true;
-						if(parseInt(cvs[0],10)>parseInt(cvs[1],10) || parseInt(cvs[0],10) < 0 || parseInt(cvs[1],10) < 0){alert("invalid values for "+s); return false;}
-					}
-					if(parseInt(ivs[0],10)>parseInt(ivs[1],10) || parseInt(ivs[0],10) < 0 || parseInt(ivs[1],10) < 0 || parseInt(ivs[2],10) < 0){alert("invalid values for "+s); return false;}
-				}
-				if(spaceSeperated){
-					for(let i of ivs){if(!/^-?[0-9.]+\s*$/.test(i)){alert("single space seperated: values must be ints or floats for "+s); return false;}}
-				}
-				if(commaSeperated){
-					for(let i of cvs){if(!/^-?[0-9.]+\s*$/.test(i)){alert("comma seperated: values must be ints or floats for "+s); return false;}}
-				}
-				if(commaSpaceSeperated){
-					for(let i of ccvs){if(!/^-?[0-9.]+\s*$/.test(i)){alert("comma & space seperated: values must be ints or floats for "+s); return false;}}
-				}
-				if(!commaSpaceSeperated && !spaceSeperated && !commaSeperated){alert("space or comma seperated values per line for "+s); return false;}
-			}
-		}
-		var textAreaArr = ["dH2OTempPairs","tempDH2OPairs","presTempPairs","tempPresPairs","lipVapSatTempVal","lipVapSatPresVal"];
-		for(let s of textAreaArr){
-			if($("textarea#"+s).is(":visible")){
-				var iv = document.getElementById(s).value;
-				var ivl = iv.trim().split(/\r|\n/);
-				for(let ivlv of ivl){
-					var ccvs = ivlv.trim().split(", ");
-					var ivs = ivlv.trim().split(",");
-					var commaSpaceSeperated = ccvs.length>1;
-					var commaSeperated = !commaSpaceSeperated && ivs.length>1;
-					if(commaSeperated){
-						for(let i of ivs){
-							if(!/^-?[0-9.]+\s*$/.test(i)){alert("values must be ints or floats for "+s); return false;}
-							if(parseInt(i,10) < 0){alert("invalid values for "+s); return false;}
-						}
-					}
-					if(commaSpaceSeperated){
-						for(let i of ccvs){
-							if(!/^-?[0-9.]+\s*$/.test(i)){alert("values must be ints or floats for "+s); return false;}
-							if(parseInt(i,10) < 0){alert("invalid values for "+s); return false;}
-						}
-					}
-					if(s==="lipVapSatPresVal" || s==="lipVapSatTempVal"){
-						if(ivs.length!=1){alert("1 value per line for "+s); return false;}
-						if(!/^-?[0-9.]+\s*$/.test(ivs)){alert("values must be ints or floats for "+s); return false;}
-					}
-					else{if(ivs.length!=2){alert("2 comma seperated values per line for "+s); return false;}}
-				}
-				if(s==="lipVapSatPresVal" || s==="lipVapSatTempVal"){
-					if(ivl[ivl.length-1]!=0){alert("must end with 0");return false;}
-				}
-				else{
-					alert("'"+ivl[ivl.length-1].toString().trim()+"'");
-					if(ivl[ivl.length-1].toString().trim()!="0,0"){alert("must end with 0,0");return false;}
-				}
-			}
-		}
-		var reactiv = document.getElementById("reaction").value.split(/\r|\n/);
-		for(let s of reactiv){
-			var ivs = s.trim().split(" ");
-      // FIXME: no preg_match in JavaScript
-			// if(preg_match("/[a-z]/i", $s) && ivs.length!=2){alert("2 space seperated values per line for reaction"); return false;}
-			if(!/^-?[0-9]+\s*$/.test(ivs[0])){alert("stoichiometric coefficient must be an int for "+s); return false;}
-		}
-		$("input:visible:text").each(function(){if($(this).val().length === 0){filledIn = false;}});
-		$("textarea:visible").each(function(){if($(this).val().length === 0){filledIn = false;}});
-		if(!filledIn){
-			alert("Please Fill in All Items");
-			return false;
-		}
-		// if(validOutput){return true;}
-		return true;
-	}
-</script>
-{% endblock %}
diff --git a/django_airavata/apps/workspace/views.py b/django_airavata/apps/workspace/views.py
index 0b69ede..bd3ca29 100644
--- a/django_airavata/apps/workspace/views.py
+++ b/django_airavata/apps/workspace/views.py
@@ -7,8 +7,10 @@ from airavata.model.application.io.ttypes import DataType
 from airavata_django_portal_sdk import user_storage as user_storage_sdk
 from django.contrib.auth.decorators import login_required
 from django.shortcuts import render
+from django.utils.module_loading import import_string
 from rest_framework.renderers import JSONRenderer
 
+from django_airavata.apps.api import models
 from django_airavata.apps.api.views import (
     ApplicationModuleViewSet,
     ExperimentSearchViewSet,
@@ -69,11 +71,6 @@ def edit_project(request, project_id):
     })
 
 
-def species_list(request):
-    return {
-        'species': ["ALMANDINE","ANDRADITE","GROSSULAR","KNORRINGITE","MAJORITE","PYROPE","SPESSARTINE","CLINOHUMITE","FAYALITE","FORSTERITE","MONTICELLITE","TEPHROITE","ANDALUSITE","KYANITE","Al-MULLITE","Si-MULLITE","Fe-CHLORITOID","Mg-CHLORITOID","Mn-CHLORITOID","Fe-STAUROLITE","Mg-STAUROLITE","Mn-STAUROLITE","HYDROXY-TOPAZ","AKERMANITE","JULGOLDITE(FeFe)","MERWINITE","PUMPELLYITE(FeAl)","PUMPELLYITE(MgAl)","RANKINITE","SPURRITE","TILLEYITE","ZIRCON","CLINOZOISITE","EPIDOTE(ORDERED)", [...]
-    }
-
 @login_required
 def create_experiment(request, app_module_id):
     request.active_nav_item = 'dashboard'
@@ -118,24 +115,51 @@ def create_experiment(request, app_module_id):
     if 'experiment-data-dir' in request.GET:
         context['experiment_data_dir'] = request.GET['experiment-data-dir']
 
-    # Run through context processors
-    for processor in [species_list]:
-        context.update(species_list(request))
-    return render(request,
-                  #   'django_airavata_workspace/create_experiment.html',
-                  'django_airavata_workspace/supcrtbl2.html',
-                  context)
+    template_path = 'django_airavata_workspace/create_experiment.html'
+    # Apply a custom application template if it exists
+    custom_template_path, custom_context = get_custom_template(request, app_module_id)
+    if custom_template_path is not None:
+        logger.debug(f"Applying custom application template {custom_template_path}")
+        template_path = custom_template_path
+        context.update(custom_context)
+
+    return render(request, template_path, context)
 
 
 @login_required
 def edit_experiment(request, experiment_id):
     request.active_nav_item = 'experiments'
 
-    return render(request,
-                  #   'django_airavata_workspace/edit_experiment.html',
-                  'django_airavata_workspace/supcrtbl2.html',
-                  {'bundle_name': 'edit-experiment',
-                   'experiment_id': experiment_id})
+    experiment = request.airavata_client.getExperiment(request.authz_token, experiment_id)
+    applicationInterface = request.airavata_client.getApplicationInterface(request.authz_token, experiment.executionId)
+    app_module_id = applicationInterface.applicationModules[0]
+    context = {
+        'bundle_name': 'edit-experiment',
+        'experiment_id': experiment_id,
+        'app_module_id': app_module_id,
+    }
+    template_path = 'django_airavata_workspace/edit_experiment.html'
+    # Apply a custom application template if it exists
+    custom_template_path, custom_context = get_custom_template(request, app_module_id)
+    if custom_template_path is not None:
+        logger.debug(f"Applying custom application template {custom_template_path}")
+        template_path = custom_template_path
+        context.update(custom_context)
+
+    return render(request, template_path, context)
+
+
+def get_custom_template(request, app_module_id):
+    template_path = None
+    context = {}
+    query = models.ApplicationTemplate.objects.filter(application_module_id=app_module_id)
+    if query.exists():
+        application_template = query.get()
+        template_path = application_template.template_path
+        for context_processor in application_template.context_processors.all():
+            context_processor = import_string(context_processor.callable_path)
+            context.update(context_processor(request))
+    return template_path, context
 
 
 @login_required