diff --git a/hmmer/views.py b/hmmer/views.py
index f2f83b700..9dfbd35f7 100644
--- a/hmmer/views.py
+++ b/hmmer/views.py
@@ -6,10 +6,12 @@
from uuid import uuid4
from os import path, makedirs, chmod, remove, symlink
from hmmer.tasks import run_hmmer_task
-from hmmer.models import HmmerQueryRecord, HmmerDB
+from hmmer.models import HmmerQueryRecord, HmmerDB, HmmerSearch
from datetime import datetime, timedelta
from pytz import timezone
from django.utils.timezone import localtime, now
+from misc.logger import i5kLogger
+from misc.get_tag import get_tag
import json
import traceback
import stat as Perm
@@ -18,6 +20,8 @@
from i5k.settings import HMMER_QUERY_MAX
from util.get_bin_name import get_bin_name
+log = i5kLogger()
+
def manual(request):
'''
@@ -40,6 +44,30 @@ def create(request):
key=lambda x: (x[3], x[1], x[0], x[2]))
hmmerdb_type_counts = dict([(k.lower().replace(' ', '_'), len(list(g))) for k, g in
groupby(sorted(hmmerdb_list, key=lambda x: x[0]), key=lambda x: x[0])])
+
+ if 'search_id' in request.GET and request.GET['search_id']:
+ print 'hmmer edit search'
+ tag = request.GET['search_id']
+ saved_search = HmmerSearch.objects.filter(search_tag=tag)
+ if saved_search:
+ saved_search = saved_search[0]
+ sequence_list = saved_search.sequence.split('\n')
+ print 'sequence %s' % sequence_list
+ print 'program %s' % saved_search.program,
+ return render(request, 'hmmer/main.html', {
+ 'tag': saved_search.search_tag,
+ 'sequence': saved_search.sequence,
+ 'program': saved_search.program,
+ 'cut_off': saved_search.cut_off,
+ 'significane_seq': float(saved_search.significane_seq),
+ 'significane_hit': float(saved_search.significane_hit),
+ 'report_seq': float(saved_search.report_seq),
+ 'report_hit': float(saved_search.report_hit),
+ 'organism': saved_search.organisms[1:-1],
+ 'title': 'HMMER Query',
+ 'hmmerdb_list': json.dumps(hmmerdb_list),
+ 'hmmerdb_type_counts': hmmerdb_type_counts,
+ })
'''
Redirect from clustal result
'''
@@ -53,11 +81,13 @@ def create(request):
for line in content_file:
clustal_content.append(line)
+ tag = get_tag('vagrant', HmmerSearch)
return render(request, 'hmmer/main.html', {
'title': 'HMMER Query',
'hmmerdb_list': json.dumps(hmmerdb_list),
'hmmerdb_type_counts': hmmerdb_type_counts,
'clustal_content': "".join(clustal_content),
+ 'tag':tag
})
elif request.method == 'POST':
@@ -157,6 +187,10 @@ def create(request):
'input': path.basename(query_filename)}, f)
args = generate_hmmer_args(request.POST['program'], program_path, query_filename, option_params, db_list)
run_hmmer_task.delay(task_id, args, file_prefix)
+ if request.user.is_authenticated():
+ save_history(request.POST, task_id, request.user, query_filename)
+ else:
+ save_history(request.POST, task_id, None, query_filename)
return redirect('hmmer:retrieve', task_id)
@@ -301,9 +335,29 @@ def generate_hmmer_args(program, program_path, query_filename,
query_filename])
for idx, db in enumerate(db_list):
args.append([path.join(program_path, 'hmmsearch'), '-o', str(idx) + '.out']
- + option_params + [query_filename + '.hmm', db])
+ + option_params + [query_filename + '.hmm', db])
else: # phmmer
for idx, db in enumerate(db_list):
args.append([path.join(program_path, 'phmmer'), '-o', str(idx) + '.out']
- + option_params + [query_filename, db])
+ + option_params + [query_filename, db])
return args
+
+
+def save_history(post, task_id, user, seq_file):
+ rec = HmmerSearch()
+ with open(seq_file) as f:
+ rec.sequence = f.read()
+ rec.task_id = task_id
+ rec.search_tag = post.get('tag')
+ rec.enqueue_date = datetime.now()
+ rec.program = post.get('program', '')
+ rec.user = user
+ rec.cut_off = post.get('cutoff', '')
+ rec.significane_seq = post.get('s_sequence', '0')
+ rec.significane_hit = post.get('s_hit', '0')
+ rec.report_seq = post.get('r_sequence', '0')
+ rec.report_hit = post.get('r_hit', '0')
+ org = post.getlist('db-name', [])
+ orgs = ','.join(org)
+ rec.organisms = json.dumps(orgs)
+ rec.save()
diff --git a/i5k/settings.py b/i5k/settings.py
index 03e1f69f1..47f195796 100755
--- a/i5k/settings.py
+++ b/i5k/settings.py
@@ -207,11 +207,6 @@
{'app': 'clustal', 'label': 'clustal', 'icon': 'icon-leaf', 'models': (
{'model': 'clustalqueryrecord'},
)},
- {'app': 'data', 'label': 'Data', 'icon': 'icon-leaf', 'models': (
- {'model': 'file'},
- {'model': 'item'},
- {'model': 'accession'},
- )},
# auth and axes
{'label': 'Auth', 'icon': 'icon-lock', 'models': (
{'model': 'auth.user'},
@@ -223,6 +218,8 @@
)
else:
suit_menu = (
+ {'app': 'app', 'label': 'Organism', 'icon':'icon-leaf', 'url': 'app.organism', 'models': (
+ )},
{'app': 'blast', 'label': 'BLAST', 'icon': 'icon-leaf', 'models': (
{'model': 'blastqueryrecord'},
{'model': 'organism'},
diff --git a/i5k/urls.py b/i5k/urls.py
index ca4123f1a..077b2baba 100644
--- a/i5k/urls.py
+++ b/i5k/urls.py
@@ -7,6 +7,7 @@
from django.contrib.auth.decorators import user_passes_test
from app.forms import BootstrapAuthenticationForm, BootStrapPasswordChangeForm, BootStrapPasswordResetForm, BootStrapSetPasswordForm
from app.views import about, set_institution, info_change, register, logout_all, password_change
+from dashboard.views import dashboard
admin.autodiscover()
# admin.site.unregister(Site)
@@ -16,6 +17,12 @@
urlpatterns = [
url(r'^about', about, name='about'),
+
+ url(r'^home$', dashboard, name='dashboard'),
+ url(r'^dashboard$', dashboard, name='dashboard'),
+ url(r'blast_hist', dashboard, name='dashboard_blast'),
+ url(r'hmmer_hist', dashboard, name='dashboard_hmmer'),
+ url(r'clustal_hist', dashboard, name='dashboard_clustal'),
url(r'^admin/filebrowser/', include('filebrowser.urls')),
# url(r'^grappelli/', include('grappelli.urls')),
# Enable admin documentation:
@@ -106,6 +113,7 @@
url(r'^clustal/', include('clustal.urls', namespace='clustal')),
]
+
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/misc/get_tag.py b/misc/get_tag.py
new file mode 100644
index 000000000..63115a8ce
--- /dev/null
+++ b/misc/get_tag.py
@@ -0,0 +1,55 @@
+'''
+ get_tag
+ return a search tag
+'''
+import enchant
+import random
+
+from misc.logger import i5kLogger
+
+log = i5kLogger()
+
+TAG_MAXTRIES = 100
+
+VOWS = 'AEIOU' # Vowel list.
+CONS = 'BCDFGHJKLMNPQRSTVWXYZ' # Consonant list.
+di = enchant.Dict("en_US") # English dictionary instance.
+
+
+def get_tag(prefix, object_list):
+ #
+ # Get a unique random search tag in the format
+ #
+ #
-
+ #
+ # not in the English dictionary, and prepend the user name to it.
+ #
+ while True:
+ b = random.randint(0, 4)
+ d = random.randint(0, 4)
+ if b != d:
+ # Skip tags with the same vowels.
+ break
+
+ found = False
+ for tri in range(1, TAG_MAXTRIES):
+ a = random.randint(0, 20)
+ c = random.randint(0, 20)
+ tag = CONS[a] + VOWS[b] + CONS[c] + VOWS[d]
+ if (di.check(tag)):
+ # Skip dictionary words.
+ continue
+ tag = prefix + '-' + tag
+ if not object_list.objects.filter(search_tag=tag).exists():
+ # Make it unique.
+ found = True
+ break
+
+ if tri > 50:
+ log.debug("get_tag: TAG tries: ---> %d" % tri)
+
+ if found:
+ return tag
+ else:
+ log.error('Run out of search tags')
+ return None
diff --git a/requirements.txt b/requirements.txt
index 75b416423..c3caf32d8 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -16,4 +16,4 @@ wsgiref==0.1.2
django-suit==0.2.26
six>=1.10.0
django-axes==2.3.3
-requests==2.18.4
+requests==2.18.4
diff --git a/setup.py b/setup.py
index 72067ee62..494f01769 100644
--- a/setup.py
+++ b/setup.py
@@ -8,6 +8,7 @@
import subprocess
from six.moves import urllib
from util.get_bin_name import get_bin_name
+import pip
PROJECT_ROOT = dirname(abspath(__file__))
@@ -55,6 +56,8 @@
rmtree(extracted_blast_path)
if platform == 'darwin':
+ # installation of pyenchant
+ pip.main(['install', 'https://pypi.python.org/packages/py2.py3.cp27.cp26.cp32.cp33.cp34.pp27/p/pyenchant/pyenchant-1.6.6-py2.py3.cp27.cp26.cp32.cp33.cp34.pp27-none-macosx_10_6_intel.macosx_10_9_intel.whl'])
# installation of hmmer
hmmer_bin_path = join(PROJECT_ROOT, 'hmmer', bin_name + '/')
@@ -115,6 +118,8 @@
chmod(clustalw_path, Perm.S_IXUSR | Perm.S_IXGRP | Perm.S_IXOTH)
remove(clustalw_dmg_path)
else: # for linux
+ # installation of pyenchant
+ pip.main(['install', 'pyenchant==1.6.11'])
# installation of hmmer
hmmer_bin_path = join(PROJECT_ROOT, 'hmmer', bin_name + '/')
diff --git a/sphinx_doc/dev_doc/dashboard.md b/sphinx_doc/dev_doc/dashboard.md
index 369d33a02..9839e9fa2 100644
--- a/sphinx_doc/dev_doc/dashboard.md
+++ b/sphinx_doc/dev_doc/dashboard.md
@@ -396,20 +396,335 @@ email us (or phone us when the helpdesk becomes available). For example:
## Logout
-- TBD
---
## Programming Structure
-The Dashboard APIs to both the front end and the Apps exchanges data in JSON format. If any sender is unable to do so, server side
-functions convert the incoming format to JSON before passing the data to the Dashboard. For example, the server converts
-form data from the browser from HTML request to a JSON object.
-- TBD
+The Dashbord UI uses the bootstrap framework, and the gridstack.js, a jQuery plugin for widget layout.
+This is drag-and-drop multi-column grid. It allows you to build draggable responsive bootstrap v3 friendly layouts.
+
+Basic usage:
+
+
+
+
+
+Options:
+
++ always_show_resize_handle - if true the resizing handles are shown even the user is not hovering over the widget (default: false)
+
++ animate - turns animation on (default: false)
+
++ auto - if false it tells to do not initialize existing items (default: true)
+
++ cell_height - one cell height (default: 60)
+
++ draggable - allows to override jQuery UI draggable options. (default: {handle: '.grid-stack-item-content', scroll: true, appendTo: 'body'})
+
++ handle - draggable handle selector (default: '.grid-stack-item-content')
+
++ height - maximum rows amount. Default is 0 which means no maximum rows
+
++ float - enable floating widgets (default: false)
+
++ item_class - widget class (default: 'grid-stack-item')
+
++ min_width - minimal width. If window width is less grid will be shown in one-column mode (default: 768)
+
++ placeholder_class - class for placeholder (default: 'grid-stack-placeholder')
+
++ resizable - allows to override jQuery UI resizable options. (default: {autoHide: true, handles: 'se'})
+
++ vertical_margin - vertical gap size (default: 20)
+
++ width - amount of columns (default: 12)
+
+Grid attributes:
+
++ data-gs-animate - turns animation on
+
++ data-gs-width - amount of columns
+
++ data-gs-height - maximum rows amount. Default is 0 which means no maximum rows.
+
+Item attributes
+
++ data-gs-x, data-gs-y - element position
+
++ data-gs-width, data-gs-height - element size
+
++ data-gs-max-width, data-gs-min-width, data-gs-max-height, data-gs-min-height - element constraints
+
++ data-gs-no-resize - disable element resizing
+
++ data-gs-no-move - disable element moving
+
++ data-gs-auto-position - tells to ignore data-gs-x and data-gs-y attributes and to place element to the first available position
+
++ data-gs-locked - the widget will be locked. It means another widgets could not move it during dragging or resizing. The widget is still can be dragged or resized. You need to add data-gs-no-resize and data-gs-no-move attributes to completely lock the widget.
+
+
+Events:
+
++ onchange(items) Occurs when widgets change their position/size
+
+ var serialize_widget_map = function (items) {
+ console.log(items);
+ };
+ $('.grid-stack').on('change', function (e, items) {
+ serialize_widget_map(items);
+ });
+
++ ondragstart(event, ui)
+
+ $('.grid-stack').on('dragstart', function (event, ui) {
+ var grid = this;
+ var element = event.target;
+ });
+
++ ondragstop(event, ui)
+
+ $('.grid-stack').on('dragstop', function (event, ui) {
+ var grid = this;
+ var element = event.target;
+ });
+
++ onresizestart(event, ui)
+
+ $('.grid-stack').on('resizestart', function (event, ui) {
+ var grid = this;
+ var element = event.target;
+ });
+
++ onresizestop(event, ui)
+
+ $('.grid-stack').on('resizestop', function (event, ui) {
+ var grid = this;
+ var element = event.target;
+ });
+
+API:
+
++ add_widget(el, x, y, width, height, auto_position)
+
+ Creates new widget and returns it.
+
+ Parameters:
+
+ - el - widget to add
+ - x, y, width, height - widget position/dimensions (Optional)
+ - auto_position - if true then x, y parameters will be ignored and widget will be places on the first available position
+
+ Widget will be always placed even if result height will be more
+ then grid height. You need to use will_it_fit method before call
+ add_widget for additional check.
+
+ $('.grid-stack').gridstack();
+
+ var grid = $('.grid-stack').data('gridstack');
+ grid.add_widget(el, 0, 0, 3, 2, true);
+
++ batch_update() Initailizes batch updates. You will see no changes until commit method is called.
+
++ cell_height() Gets current cell height.
+
++ cell_height(val)
+
+ Update current cell height. This method rebuilds an internal CSS stylesheet.
+ Note: You can expect performance issues if call this method too often.
+
+ grid.cell_height(grid.cell_width() * 1.2);
+
++ cell_width() Gets current cell width.
+
++ commit() Finishes batch updates. Updates DOM nodes. You must call it after batch_update.
+
++ disable() Disables widgets moving/resizing. This is a shortcut for:
+
+ grid.movable('.grid-stack-item', false);
+ grid.resizable('.grid-stack-item', false);
+
++ enable() Enables widgets moving/resizing. This is a shortcut for:
+
+ grid.movable('.grid-stack-item', true);
+ grid.resizable('.grid-stack-item', true);
+
+get_cell_from_pixel(position) Get the position of the cell under a pixel on screen.
+
+ Parameters :
+
+ - position - the position of the pixel to resolve in absolute coordinates, as an object with top and leftproperties
+
+ Returns an object with properties x and y i.e. the column and row in the grid.
+
++ is_area_empty(x, y, width, height) Checks if specified area is empty.
+
++ locked(el, val) Locks/unlocks widget.
+
+ - el - widget to modify.
+ - val - if true widget will be locked.
+
++ remove_widget(el, detach_node) Removes widget from the grid.
+
+ Parameters:
+
+ - el - widget to remove.
+ - detach_node - if false DOM node will not be removed from the tree (Optional. Default true).
+
++ remove_all() Removes all widgets from the grid.
+
++ resize(el, width, height) Changes widget size
+
+ Parameters:
+
+ - el - widget to resize
+ - width, height - new dimensions. If value is null or undefined it will be ignored.
+
+
++ move(el, x, y) Changes widget position
+
+ Parameters:
+
+ - el - widget to move
+ - x, y - new position. If value is null or undefined it will be ignored.
+
++ resizable(el, val) Enables/Disables resizing.
+
+ - el - widget to modify
+ - val - if true widget will be resizable.
+
++ movable(el, val) Enables/Disables moving.
+
+ - el - widget to modify
+ - val - if true widget will be draggable.
+
++ update(el, x, y, width, height)
+
+ Parameters:
+
+ - el - widget to move
+ - x, y - new position. If value is null or undefined it will be ignored.
+ - width, height - new dimensions. If value is null or undefined it will be ignored.
+
+ Updates widget position/size.
+ will_it_fit(x, y, width, height, auto_position)
+
+ Returns true if the height of the grid will be less the vertical constraint.
+ Always returns true if grid does not have height constraint.
+
+ if (grid.will_it_fit(new_node.x, new_node.y, new_node.width, new_node.height, true)) {
+ grid.add_widget(new_node.x, new_node.y, new_node.width, new_node.height, true);
+ } else {
+ alert('Not enough free space to place the widget');
+ }
+
+Utils:
+
++ GridStackUI.Utils.sort(nodes, dir, width) Sorts array of nodes
+
+ - nodes - array to sort
+ - dir - 1 for asc, -1 for desc (optional)
+ - width - width of the grid. If undefined the width will be calculated automatically (optional).
+
+
+Save grid to array:
+
+Because gridstack does not track any kind of user-defined widget id there is no reason to make serialization to be part of gridstack API. To serialize grid you can simply do something like this (let us say you store widget id inside data-custom-id attribute):
+
+ var res = _.map($('.grid-stack .grid-stack-item:visible'), function (el) {
+ el = $(el);
+ var node = el.data('_gridstack_node');
+ return {
+ id: el.attr('data-custom-id'),
+ x: node.x,
+ y: node.y,
+ width: node.width,
+ height: node.height
+ };
+ });
+ alert(JSON.stringify(res));
+
+To run apps inside widgets use an iframe with width and height == 100%.
+
+
+
+
+
+
+#### Touch devices support
+
+ TBD
### Authentication Subsystem
-The Authentication Subsystem provides the functionality for users to login and logout, and for the registration process to add new users and their
+ The Authentication Subsystem provides the functionality for users to login and logout, and for the registration process to add new users and their
credentials to the site.
#### Single Sign-On