diff --git a/.gitignore b/.gitignore
index 08856943..04f3b490 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,5 +18,5 @@ import_log.txt
# Deployment files
.env.prod
-static/
+/static/
media/
diff --git a/database/static/database/jquery.formset.js b/database/static/database/jquery.formset.js
new file mode 100644
index 00000000..e0e628e6
--- /dev/null
+++ b/database/static/database/jquery.formset.js
@@ -0,0 +1,250 @@
+/**
+ * jQuery Formset 1.5-pre
+ * @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com)
+ * @requires jQuery 1.2.6 or later
+ *
+ * Copyright (c) 2009, Stanislaus Madueke
+ * All rights reserved.
+ *
+ * Licensed under the New BSD License
+ * See: http://www.opensource.org/licenses/bsd-license.php
+ */
+;(function($) {
+ $.fn.formset = function(opts)
+ {
+ var options = $.extend({}, $.fn.formset.defaults, opts),
+ flatExtraClasses = options.extraClasses.join(' '),
+ totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'),
+ maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'),
+ minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS'),
+ childElementSelector = 'input,select,textarea,label,div',
+ $$ = $(this),
+
+ applyExtraClasses = function(row, ndx) {
+ if (options.extraClasses) {
+ row.removeClass(flatExtraClasses);
+ row.addClass(options.extraClasses[ndx % options.extraClasses.length]);
+ }
+ },
+
+ updateElementIndex = function(elem, prefix, ndx) {
+ var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-'),
+ replacement = prefix + '-' + ndx + '-';
+ if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement));
+ if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement));
+ if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement));
+ },
+
+ hasChildElements = function(row) {
+ return row.find(childElementSelector).length > 0;
+ },
+
+ showAddButton = function() {
+ return maxForms.length == 0 || // For Django versions pre 1.2
+ (maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0));
+ },
+
+ /**
+ * Indicates whether delete link(s) can be displayed - when total forms > min forms
+ */
+ showDeleteLinks = function() {
+ return minForms.length == 0 || // For Django versions pre 1.7
+ (minForms.val() == '' || (totalForms.val() - minForms.val() > 0));
+ },
+
+ insertDeleteLink = function(row) {
+ var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'),
+ addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.');
+
+ var delButtonHTML = '' + options.deleteText +'';
+ if (options.deleteContainerClass) {
+ // If we have a specific container for the remove button,
+ // place it as the last child of that container:
+ row.find('[class*="' + options.deleteContainerClass + '"]').append(delButtonHTML);
+ } else if (row.is('TR')) {
+ // If the forms are laid out in table rows, insert
+ // the remove button into the last table cell:
+ row.children(':last').append(delButtonHTML);
+ } else if (row.is('UL') || row.is('OL')) {
+ // If they're laid out as an ordered/unordered list,
+ // insert an
after the last list item:
+ row.append('
' + delButtonHTML + '
');
+ } else {
+ // Otherwise, just insert the remove button as the
+ // last child element of the form's container:
+ row.append(delButtonHTML);
+ }
+
+ // Check if we're under the minimum number of forms - not to display delete link at rendering
+ if (!showDeleteLinks()){
+ row.find('a.' + delCssSelector).hide();
+ }
+
+ row.find('a.' + delCssSelector).click(function() {
+ var row = $(this).parents('.' + options.formCssClass),
+ del = row.find('input:hidden[id $= "-DELETE"]'),
+ buttonRow = row.siblings("a." + addCssSelector + ', .' + options.formCssClass + '-add'),
+ forms;
+ if (del.length) {
+ // We're dealing with an inline formset.
+ // Rather than remove this form from the DOM, we'll mark it as deleted
+ // and hide it, then let Django handle the deleting:
+ del.val('on');
+ row.hide();
+ forms = $('.' + options.formCssClass).not(':hidden');
+ totalForms.val(forms.length);
+ } else {
+ row.remove();
+ // Update the TOTAL_FORMS count:
+ forms = $('.' + options.formCssClass).not('.formset-custom-template');
+ totalForms.val(forms.length);
+ }
+ for (var i=0, formCount=forms.length; i');
+ row.hide();
+ } else {
+ del.before('');
+ }
+ // Hide any labels associated with the DELETE checkbox:
+ $('label[for="' + del.attr('id') + '"]').hide();
+ del.remove();
+ }
+ if (hasChildElements(row)) {
+ row.addClass(options.formCssClass);
+ if (row.is(':visible')) {
+ insertDeleteLink(row);
+ applyExtraClasses(row, i);
+ }
+ }
+ });
+
+ if ($$.length) {
+ var hideAddButton = !showAddButton(),
+ addButton, template;
+ if (options.formTemplate) {
+ // If a form template was specified, we'll clone it to generate new form instances:
+ template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate);
+ template.removeAttr('id').addClass(options.formCssClass + ' formset-custom-template');
+ template.find(childElementSelector).each(function() {
+ updateElementIndex($(this), options.prefix, '__prefix__');
+ });
+ insertDeleteLink(template);
+ } else {
+ // Otherwise, use the last form in the formset; this works much better if you've got
+ // extra (>= 1) forms (thnaks to justhamade for pointing this out):
+ if (options.hideLastAddForm) $('.' + options.formCssClass + ':last').hide();
+ template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');
+ template.find('input:hidden[id $= "-DELETE"]').remove();
+ // Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion):
+ template.find(childElementSelector).not(options.keepFieldValues).each(function() {
+ var elem = $(this);
+ // If this is a checkbox or radiobutton, uncheck it.
+ // This fixes Issue 1, reported by Wilson.Andrew.J:
+ if (elem.is('input:checkbox') || elem.is('input:radio')) {
+ elem.attr('checked', false);
+ } else {
+ elem.val('');
+ }
+ });
+ }
+ // FIXME: Perhaps using $.data would be a better idea?
+ options.formTemplate = template;
+
+ var addButtonHTML = '' + options.addText + '';
+ if (options.addContainerClass) {
+ // If we have a specific container for the "add" button,
+ // place it as the last child of that container:
+ var addContainer = $('[class*="' + options.addContainerClass + '"');
+ addContainer.append(addButtonHTML);
+ addButton = addContainer.find('[class="' + options.addCssClass + '"]');
+ } else if ($$.is('TR')) {
+ // If forms are laid out as table rows, insert the
+ // "add" button in a new table row:
+ var numCols = $$.eq(0).children().length, // This is a bit of an assumption :|
+ buttonRow = $('
' + addButtonHTML + '
').addClass(options.formCssClass + '-add');
+ $$.parent().append(buttonRow);
+ addButton = buttonRow.find('a');
+ } else {
+ // Otherwise, insert it immediately after the last form:
+ $$.filter(':last').after(addButtonHTML);
+ addButton = $$.filter(':last').next();
+ }
+
+ if (hideAddButton) addButton.hide();
+
+ addButton.click(function() {
+ var formCount = parseInt(totalForms.val()),
+ row = options.formTemplate.clone(true).removeClass('formset-custom-template'),
+ buttonRow = $($(this).parents('tr.' + options.formCssClass + '-add').get(0) || this),
+ delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.');
+ applyExtraClasses(row, formCount);
+ row.insertBefore(buttonRow).show();
+ row.find(childElementSelector).each(function() {
+ updateElementIndex($(this), options.prefix, formCount);
+ });
+ totalForms.val(formCount + 1);
+ // Check if we're above the minimum allowed number of forms -> show all delete link(s)
+ if (showDeleteLinks()){
+ $('a.' + delCssSelector).each(function(){$(this).show();});
+ }
+ // Check if we've exceeded the maximum allowed number of forms:
+ if (!showAddButton()) buttonRow.hide();
+ // If a post-add callback was supplied, call it with the added form:
+ if (options.added) options.added(row);
+ return false;
+ });
+ }
+
+ return $$;
+ };
+
+ /* Setup plugin defaults */
+ $.fn.formset.defaults = {
+ prefix: 'form', // The form prefix for your django formset
+ formTemplate: null, // The jQuery selection cloned to generate new form instances
+ addText: 'add another', // Text for the add link
+ deleteText: 'remove', // Text for the delete link
+ addContainerClass: null, // Container CSS class for the add link
+ deleteContainerClass: null, // Container CSS class for the delete link
+ addCssClass: 'add-row btn btn-outline-primary', // CSS class applied to the add link
+ deleteCssClass: 'delete-row btn btn-outline-danger', // CSS class applied to the delete link
+ formCssClass: 'dynamic-form', // CSS class applied to each form in a formset
+ extraClasses: [], // Additional CSS classes, which will be applied to each form in turn
+ keepFieldValues: '', // jQuery selector for fields whose values should be kept when the form is cloned
+ added: null, // Function called each time a new form is added
+ removed: null, // Function called each time a form is deleted
+ hideLastAddForm: false // When set to true, hide last empty add form (becomes visible when clicking on add button)
+ };
+})(jQuery);
diff --git a/database/templates/database/base.html b/database/templates/database/base.html
index f992f83b..9ccd24f7 100644
--- a/database/templates/database/base.html
+++ b/database/templates/database/base.html
@@ -42,6 +42,7 @@
crossorigin="anonymous"
>
+
{% if title %}
Kinetic Models Site - {{ title }}
{% else %}
diff --git a/database/templates/database/revision.html b/database/templates/database/revision.html
index affdad72..9e8c0209 100644
--- a/database/templates/database/revision.html
+++ b/database/templates/database/revision.html
@@ -4,17 +4,24 @@
{% block content %}
{{ name }} {{ object.id }} Revision
-
+
{% endblock %}
diff --git a/database/views.py b/database/views.py
index ffc53062..49fd1de4 100644
--- a/database/views.py
+++ b/database/views.py
@@ -476,7 +476,7 @@ def get_queryset(self):
queryset = Species.objects.all()
if self.q:
- for query in ["speciesname__name__istartswith", "isomers__formula", "id"]:
+ for query in ["speciesname__name__istartswith", "isomers__formula__formula", "id"]:
try:
filtered = queryset.filter(**{query: self.q})
if filtered: