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/06/18 19:38:35 UTC

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

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 c26ec9127beaaf811156523eecf49cbd5cbdd6ce
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