galaxy-commits
Threads by month
- ----- 2026 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- 15302 discussions
commit/galaxy-central: guerler: ToolForm: Generate json for tools api, improve ui-tabs
by commits-noreply@bitbucket.org 08 Sep '14
by commits-noreply@bitbucket.org 08 Sep '14
08 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/95b3be32cac3/
Changeset: 95b3be32cac3
User: guerler
Date: 2014-09-08 22:52:28
Summary: ToolForm: Generate json for tools api, improve ui-tabs
Affected #: 6 files
diff -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 -r 95b3be32cac3b11937fcc8813fb27a58ce0c18ca static/scripts/mvc/tools/tools-form.js
--- a/static/scripts/mvc/tools/tools-form.js
+++ b/static/scripts/mvc/tools/tools-form.js
@@ -157,6 +157,7 @@
// trigger refresh
self.refresh();
+ self._submit();
}
});
},
@@ -177,7 +178,7 @@
// submit
_submit: function() {
- console.log(this.tree.jobDictionary());
+ console.log(this.tree.finalize());
}
});
diff -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 -r 95b3be32cac3b11937fcc8813fb27a58ce0c18ca static/scripts/mvc/tools/tools-tree.js
--- a/static/scripts/mvc/tools/tools-tree.js
+++ b/static/scripts/mvc/tools/tools-tree.js
@@ -11,24 +11,23 @@
// creates tree structure
refresh: function() {
- // check if section is available
- if (!this.app.section) {
- return {};
- }
-
// create dictionary
this.dict = {};
// create xml object
this.xml = $('<div/>');
+ // check if section is available
+ if (!this.app.section) {
+ return {};
+ }
+
// fill dictionary
this._iterate(this.app.section.$el, this.dict, this.xml);
},
-
// convert to job dictionary
- jobDictionary: function() {
+ finalize: function() {
// link this
var self = this;
@@ -38,22 +37,48 @@
// converter between raw dictionary and job dictionary
function convert(identifier, head) {
for (var i in head) {
- if (head[i].input) {
- var input = head[i].input;
- var job_input_id = identifier + input.name;
+ var node = head[i];
+ if (node.input) {
+ // get node
+ var input = node.input;
- if (input.type == 'repeat') {
-
+ // create identifier
+ var job_input_id = identifier;
+ if (identifier != '') {
+ job_input_id += '|';
+ }
+ job_input_id += input.name;
- } else {
- //var value = self.app.field_list[input.id].value();
- switch(input.type) {
- case 'repeat':
- job_def[job_input_id] = value;
- break;
- default:
- job_def[job_input_id] = value;
- }
+ // process input type
+ switch (input.type) {
+ // handle repeats
+ case 'repeat':
+ var index = 0;
+ for (var j in node) {
+ if (j.indexOf('section') != -1) {
+ convert(job_input_id + '_' + index++, node[j]);
+ }
+ }
+ break;
+ // handle conditionals
+ case 'conditional':
+ // get conditional value
+ var value = self.app.field_list[input.id].value();
+
+ // find selected case
+ for (var j in input.cases) {
+ if (input.cases[j].value == value) {
+ convert(job_input_id, head[input.id + '-section-' + j]);
+ break;
+ }
+ }
+
+ // break
+ break;
+ default:
+ // handle default value
+ var value = self.app.field_list[input.id].value();
+ job_def[job_input_id] = value;
}
}
}
@@ -61,49 +86,10 @@
// start conversion
convert('', this.dict);
-
+
// return result
return job_def;
},
-
- // iterate
- _iterate: function(parent, dict, xml) {
- // get child nodes
- var self = this;
- var children = $(parent).children();
- children.each(function() {
- // get child element
- var child = this;
-
- // get id
- var id = $(child).attr('id');
-
- // create new branch
- if ($(child).hasClass('section-row') || $(child).hasClass('tab-pane')) {
- // create sub dictionary
- dict[id] = {};
-
- // add input element if it exists
- var input = self.app.input_list[id];
- if (input) {
- dict[id] = {
- input : input
- }
- }
-
- // create xml element
- var $el = $('<div id="' + id + '"/>');
-
- // append xml
- xml.append($el);
-
- // fill sub dictionary
- self._iterate(child, dict[id], $el);
- } else {
- self._iterate(child, dict, xml);
- }
- });
- },
// find referenced elements
findReferences: function(identifier, type) {
@@ -176,6 +162,45 @@
// return
return referenced;
+ },
+
+ // iterate
+ _iterate: function(parent, dict, xml) {
+ // get child nodes
+ var self = this;
+ var children = $(parent).children();
+ children.each(function() {
+ // get child element
+ var child = this;
+
+ // get id
+ var id = $(child).attr('id');
+
+ // create new branch
+ if ($(child).hasClass('section-row') || $(child).hasClass('tab-pane')) {
+ // create sub dictionary
+ dict[id] = {};
+
+ // add input element if it exists
+ var input = self.app.input_list[id];
+ if (input) {
+ dict[id] = {
+ input : input
+ }
+ }
+
+ // create xml element
+ var $el = $('<div id="' + id + '"/>');
+
+ // append xml
+ xml.append($el);
+
+ // fill sub dictionary
+ self._iterate(child, dict[id], $el);
+ } else {
+ self._iterate(child, dict, xml);
+ }
+ });
}
});
diff -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 -r 95b3be32cac3b11937fcc8813fb27a58ce0c18ca static/scripts/mvc/ui/ui-tabs.js
--- a/static/scripts/mvc/ui/ui-tabs.js
+++ b/static/scripts/mvc/ui/ui-tabs.js
@@ -20,7 +20,8 @@
this.$nav = null;
this.$content = null;
this.first_tab = null;
-
+ this.current_id = null;
+
// configure options
this.options = Utils.merge(options, this.optionsDefault);
@@ -139,6 +140,11 @@
self.show(id);
}
});
+
+ // initialize current id
+ if (!this.current_id) {
+ this.current_id = id;
+ }
},
// delete tab
@@ -183,10 +189,14 @@
// show selected tab
if (id) {
- this.$el.find('.tab-element').removeClass('active');
- this.$el.find('.tab-pane').removeClass('active');
+ // reassign active class
+ this.$el.find('#tab-' + this.current_id).removeClass('active');
+ this.$el.find('#' + this.current_id).removeClass('active');
this.$el.find('#tab-' + id).addClass('active');
this.$el.find('#' + id).addClass('active');
+
+ // update current id
+ this.current_id = id;
}
// change
diff -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 -r 95b3be32cac3b11937fcc8813fb27a58ce0c18ca static/scripts/packed/mvc/tools/tools-form.js
--- a/static/scripts/packed/mvc/tools/tools-form.js
+++ b/static/scripts/packed/mvc/tools/tools-form.js
@@ -1,1 +1,1 @@
-define(["mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/citation/citation-model","mvc/citation/citation-view","mvc/tools","mvc/tools/tools-template","mvc/tools/tools-datasets","mvc/tools/tools-section","mvc/tools/tools-tree"],function(g,k,i,a,f,d,h,j,c){var e=Backbone.Model.extend({initialize:function(l){this.url=galaxy_config.root+"api/tools/"+l.id+"?io_details=true"}});var b=Backbone.View.extend({main_el:"body",initialize:function(m){var l=this;this.options=m;this.model=new e({id:m.id});this.tree=new c(this);this.field_list={};this.input_list={};this.datasets=new h({history_id:this.options.history_id,success:function(){l._initializeToolForm()}})},_initializeToolForm:function(){var m=this;var n=new k.ButtonIcon({icon:"fa-question-circle",title:"Question?",tooltip:"Ask a question about this tool (Biostar)",onclick:function(){window.open(m.options.biostar_url+"/p/new/post/")}});var o=new k.ButtonIcon({icon:"fa-search",title:"Search",tooltip:"Search help for this tool (Biostar)",onclick:function(){window.open(m.options.biostar_url+"/t/"+m.options.id+"/")}});var l=new k.ButtonIcon({icon:"fa-share",title:"Share",tooltip:"Share this tool",onclick:function(){prompt("Copy to clipboard: Ctrl+C, Enter",galaxy_config.root+"root?tool_id="+m.options.id)}});this.model.fetch({error:function(p){console.debug("tools-form::_initializeToolForm() : Attempt to fetch tool model failed.")},success:function(){m.inputs=m.model.get("inputs");m.portlet=new g.View({icon:"fa-wrench",title:"<b>"+m.model.get("name")+"</b> "+m.model.get("description"),buttons:{execute:new k.ButtonIcon({icon:"fa-check",tooltip:"Execute the tool",title:"Execute",floating:"clear",onclick:function(){m._submit()}})},operations:{button_question:n,button_search:o,button_share:l}});if(!m.options.biostar_url){n.$el.hide();o.$el.hide()}m.message=new k.Message();m.portlet.append(m.message.$el);$(m.main_el).append(m.portlet.$el);if(m.options.help!=""){$(m.main_el).append(d.help(m.options.help))}if(m.options.citations){$(m.main_el).append(d.citations());var p=new i.ToolCitationCollection();p.tool_id=m.options.id;var q=new a.CitationListView({collection:p});q.render();p.fetch()}m.setElement(m.portlet.content());m.section=new j.View(m,{inputs:m.model.get("inputs")});m.portlet.append(m.section.$el);m.refresh()}})},refresh:function(){this.tree.refresh();for(var l in this.field_list){this.field_list[l].trigger("change")}console.debug("tools-form::refresh() - Recreated tree structure. Refresh.")},_submit:function(){console.log(this.tree.jobDictionary())}});return{View:b}});
\ No newline at end of file
+define(["mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/citation/citation-model","mvc/citation/citation-view","mvc/tools","mvc/tools/tools-template","mvc/tools/tools-datasets","mvc/tools/tools-section","mvc/tools/tools-tree"],function(g,k,i,a,f,d,h,j,c){var e=Backbone.Model.extend({initialize:function(l){this.url=galaxy_config.root+"api/tools/"+l.id+"?io_details=true"}});var b=Backbone.View.extend({main_el:"body",initialize:function(m){var l=this;this.options=m;this.model=new e({id:m.id});this.tree=new c(this);this.field_list={};this.input_list={};this.datasets=new h({history_id:this.options.history_id,success:function(){l._initializeToolForm()}})},_initializeToolForm:function(){var m=this;var n=new k.ButtonIcon({icon:"fa-question-circle",title:"Question?",tooltip:"Ask a question about this tool (Biostar)",onclick:function(){window.open(m.options.biostar_url+"/p/new/post/")}});var o=new k.ButtonIcon({icon:"fa-search",title:"Search",tooltip:"Search help for this tool (Biostar)",onclick:function(){window.open(m.options.biostar_url+"/t/"+m.options.id+"/")}});var l=new k.ButtonIcon({icon:"fa-share",title:"Share",tooltip:"Share this tool",onclick:function(){prompt("Copy to clipboard: Ctrl+C, Enter",galaxy_config.root+"root?tool_id="+m.options.id)}});this.model.fetch({error:function(p){console.debug("tools-form::_initializeToolForm() : Attempt to fetch tool model failed.")},success:function(){m.inputs=m.model.get("inputs");m.portlet=new g.View({icon:"fa-wrench",title:"<b>"+m.model.get("name")+"</b> "+m.model.get("description"),buttons:{execute:new k.ButtonIcon({icon:"fa-check",tooltip:"Execute the tool",title:"Execute",floating:"clear",onclick:function(){m._submit()}})},operations:{button_question:n,button_search:o,button_share:l}});if(!m.options.biostar_url){n.$el.hide();o.$el.hide()}m.message=new k.Message();m.portlet.append(m.message.$el);$(m.main_el).append(m.portlet.$el);if(m.options.help!=""){$(m.main_el).append(d.help(m.options.help))}if(m.options.citations){$(m.main_el).append(d.citations());var p=new i.ToolCitationCollection();p.tool_id=m.options.id;var q=new a.CitationListView({collection:p});q.render();p.fetch()}m.setElement(m.portlet.content());m.section=new j.View(m,{inputs:m.model.get("inputs")});m.portlet.append(m.section.$el);m.refresh();m._submit()}})},refresh:function(){this.tree.refresh();for(var l in this.field_list){this.field_list[l].trigger("change")}console.debug("tools-form::refresh() - Recreated tree structure. Refresh.")},_submit:function(){console.log(this.tree.finalize())}});return{View:b}});
\ No newline at end of file
diff -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 -r 95b3be32cac3b11937fcc8813fb27a58ce0c18ca static/scripts/packed/mvc/tools/tools-tree.js
--- a/static/scripts/packed/mvc/tools/tools-tree.js
+++ b/static/scripts/packed/mvc/tools/tools-tree.js
@@ -1,1 +1,1 @@
-define([],function(){return Backbone.Model.extend({initialize:function(a){this.app=a},refresh:function(){if(!this.app.section){return{}}this.dict={};this.xml=$("<div/>");this._iterate(this.app.section.$el,this.dict,this.xml)},jobDictionary:function(){var a=this;var b={};function c(e,g){for(var f in g){if(g[f].input){var d=g[f].input;var h=e+d.name;if(d.type=="repeat"){}else{switch(d.type){case"repeat":b[h]=value;break;default:b[h]=value}}}}}c("",this.dict);return b},_iterate:function(d,e,b){var a=this;var c=$(d).children();c.each(function(){var i=this;var h=$(i).attr("id");if($(i).hasClass("section-row")||$(i).hasClass("tab-pane")){e[h]={};var f=a.app.input_list[h];if(f){e[h]={input:f}}var g=$('<div id="'+h+'"/>');b.append(g);a._iterate(i,e[h],g)}else{a._iterate(i,e,b)}})},findReferences:function(c,e){var g=[];var b=this;function d(h,j){var i=$(j).children();var l=[];var k=false;i.each(function(){var o=this;var n=$(o).attr("id");if(n!==c){var m=b.app.input_list[n];if(m){if(m.name==h){k=true;return false}if(m.data_ref==h&&m.type==e){l.push(n)}}}});if(!k){g=g.concat(l);i.each(function(){d(h,this)})}}var f=this.xml.find("#"+c);if(f.length>0){var a=this.app.input_list[c];if(a){d(a.name,f.parent())}}return g}})});
\ No newline at end of file
+define([],function(){return Backbone.Model.extend({initialize:function(a){this.app=a},refresh:function(){this.dict={};this.xml=$("<div/>");if(!this.app.section){return{}}this._iterate(this.app.section.$el,this.dict,this.xml)},finalize:function(){var a=this;var b={};function c(k,l){for(var f in l){var d=l[f];if(d.input){var m=d.input;var h=k;if(k!=""){h+="|"}h+=m.name;switch(m.type){case"repeat":var g=0;for(var e in d){if(e.indexOf("section")!=-1){c(h+"_"+g++,d[e])}}break;case"conditional":var n=a.app.field_list[m.id].value();for(var e in m.cases){if(m.cases[e].value==n){c(h,l[m.id+"-section-"+e]);break}}break;default:var n=a.app.field_list[m.id].value();b[h]=n}}}}c("",this.dict);return b},findReferences:function(c,e){var g=[];var b=this;function d(h,j){var i=$(j).children();var l=[];var k=false;i.each(function(){var o=this;var n=$(o).attr("id");if(n!==c){var m=b.app.input_list[n];if(m){if(m.name==h){k=true;return false}if(m.data_ref==h&&m.type==e){l.push(n)}}}});if(!k){g=g.concat(l);i.each(function(){d(h,this)})}}var f=this.xml.find("#"+c);if(f.length>0){var a=this.app.input_list[c];if(a){d(a.name,f.parent())}}return g},_iterate:function(d,e,b){var a=this;var c=$(d).children();c.each(function(){var i=this;var h=$(i).attr("id");if($(i).hasClass("section-row")||$(i).hasClass("tab-pane")){e[h]={};var f=a.app.input_list[h];if(f){e[h]={input:f}}var g=$('<div id="'+h+'"/>');b.append(g);a._iterate(i,e[h],g)}else{a._iterate(i,e,b)}})}})});
\ No newline at end of file
diff -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 -r 95b3be32cac3b11937fcc8813fb27a58ce0c18ca static/scripts/packed/mvc/ui/ui-tabs.js
--- a/static/scripts/packed/mvc/ui/ui-tabs.js
+++ b/static/scripts/packed/mvc/ui/ui-tabs.js
@@ -1,1 +1,1 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{title_new:"",operations:null,onnew:null,min:null,max:null,onchange:null},initialize:function(e){this.visible=false;this.$nav=null;this.$content=null;this.first_tab=null;this.options=a.merge(e,this.optionsDefault);var c=$(this._template(this.options));this.$nav=c.find(".tab-navigation");this.$content=c.find(".tab-content");this.setElement(c);this.list={};var d=this;if(this.options.operations){$.each(this.options.operations,function(g,h){h.$el.prop("id",g);d.$nav.find(".operations").append(h.$el)})}if(this.options.onnew){var f=$(this._template_tab_new(this.options));this.$nav.append(f);f.tooltip({title:"Add a new tab",placement:"bottom",container:d.$el});f.on("click",function(g){f.tooltip("hide");d.options.onnew()})}},size:function(){return _.size(this.list)},current:function(){return this.$el.find(".tab-pane.active").attr("id")},add:function(f){var e=this;var h=f.id;var g=$(this._template_tab(f));var d=$(this._template_tab_content(f));this.list[h]=f.ondel?true:false;if(this.options.onnew){this.$nav.find("#new-tab").before(g)}else{this.$nav.append(g)}d.append(f.$el);this.$content.append(d);if(this.size()==1){g.addClass("active");d.addClass("active");this.first_tab=h}if(this.options.max&&this.size()>=this.options.max){this.$el.find("#new-tab").hide()}if(f.ondel){var c=g.find("#delete");c.tooltip({title:"Delete this tab",placement:"bottom",container:e.$el});c.on("click",function(){c.tooltip("destroy");e.$el.find(".tooltip").remove();f.ondel();return false})}g.on("click",function(i){i.preventDefault();if(f.onclick){f.onclick()}else{e.show(h)}})},del:function(c){this.$el.find("#tab-"+c).remove();this.$el.find("#"+c).remove();if(this.first_tab==c){this.first_tab=null}if(this.first_tab!=null){this.show(this.first_tab)}if(this.list[c]){delete this.list[c]}if(this.size()<this.options.max){this.$el.find("#new-tab").show()}},delRemovable:function(){for(var c in this.list){this.del(c)}},show:function(c){this.$el.fadeIn("fast");this.visible=true;if(c){this.$el.find(".tab-element").removeClass("active");this.$el.find(".tab-pane").removeClass("active");this.$el.find("#tab-"+c).addClass("active");this.$el.find("#"+c).addClass("active")}if(this.options.onchange){this.options.onchange(c)}},hide:function(){this.$el.fadeOut("fast");this.visible=false},hideOperation:function(c){this.$nav.find("#"+c).hide()},showOperation:function(c){this.$nav.find("#"+c).show()},setOperation:function(e,d){var c=this.$nav.find("#"+e);c.off("click");c.on("click",d)},title:function(e,d){var c=this.$el.find("#tab-title-text-"+e);if(d){c.html(d)}return c.html()},retitle:function(d){var c=0;for(var e in this.list){this.title(e,++c+": "+d)}},_template:function(c){return'<div class="ui-tabs tabbable tabs-left"><ul id="tab-navigation" class="tab-navigation nav nav-tabs"><div class="operations" style="float: right; margin-bottom: 4px;"></div></ul><div id="tab-content" class="tab-content"/></div>'},_template_tab_new:function(c){return'<li id="new-tab"><a href="javascript:void(0);"><i class="ui-tabs-add fa fa-plus-circle"/>'+c.title_new+"</a></li>"},_template_tab:function(d){var c='<li id="tab-'+d.id+'" class="tab-element"><a id="tab-title-link-'+d.id+'" title="" href="#'+d.id+'" data-original-title=""><span id="tab-title-text-'+d.id+'" class="tab-title-text">'+d.title+"</span>";if(d.ondel){c+='<i id="delete" class="ui-tabs-delete fa fa-minus-circle"/>'}c+="</a></li>";return c},_template_tab_content:function(c){return'<div id="'+c.id+'" class="tab-pane"/>'}});return{View:b}});
\ No newline at end of file
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{title_new:"",operations:null,onnew:null,min:null,max:null,onchange:null},initialize:function(e){this.visible=false;this.$nav=null;this.$content=null;this.first_tab=null;this.current_id=null;this.options=a.merge(e,this.optionsDefault);var c=$(this._template(this.options));this.$nav=c.find(".tab-navigation");this.$content=c.find(".tab-content");this.setElement(c);this.list={};var d=this;if(this.options.operations){$.each(this.options.operations,function(g,h){h.$el.prop("id",g);d.$nav.find(".operations").append(h.$el)})}if(this.options.onnew){var f=$(this._template_tab_new(this.options));this.$nav.append(f);f.tooltip({title:"Add a new tab",placement:"bottom",container:d.$el});f.on("click",function(g){f.tooltip("hide");d.options.onnew()})}},size:function(){return _.size(this.list)},current:function(){return this.$el.find(".tab-pane.active").attr("id")},add:function(f){var e=this;var h=f.id;var g=$(this._template_tab(f));var d=$(this._template_tab_content(f));this.list[h]=f.ondel?true:false;if(this.options.onnew){this.$nav.find("#new-tab").before(g)}else{this.$nav.append(g)}d.append(f.$el);this.$content.append(d);if(this.size()==1){g.addClass("active");d.addClass("active");this.first_tab=h}if(this.options.max&&this.size()>=this.options.max){this.$el.find("#new-tab").hide()}if(f.ondel){var c=g.find("#delete");c.tooltip({title:"Delete this tab",placement:"bottom",container:e.$el});c.on("click",function(){c.tooltip("destroy");e.$el.find(".tooltip").remove();f.ondel();return false})}g.on("click",function(i){i.preventDefault();if(f.onclick){f.onclick()}else{e.show(h)}});if(!this.current_id){this.current_id=h}},del:function(c){this.$el.find("#tab-"+c).remove();this.$el.find("#"+c).remove();if(this.first_tab==c){this.first_tab=null}if(this.first_tab!=null){this.show(this.first_tab)}if(this.list[c]){delete this.list[c]}if(this.size()<this.options.max){this.$el.find("#new-tab").show()}},delRemovable:function(){for(var c in this.list){this.del(c)}},show:function(c){this.$el.fadeIn("fast");this.visible=true;if(c){this.$el.find("#tab-"+this.current_id).removeClass("active");this.$el.find("#"+this.current_id).removeClass("active");this.$el.find("#tab-"+c).addClass("active");this.$el.find("#"+c).addClass("active");this.current_id=c}if(this.options.onchange){this.options.onchange(c)}},hide:function(){this.$el.fadeOut("fast");this.visible=false},hideOperation:function(c){this.$nav.find("#"+c).hide()},showOperation:function(c){this.$nav.find("#"+c).show()},setOperation:function(e,d){var c=this.$nav.find("#"+e);c.off("click");c.on("click",d)},title:function(e,d){var c=this.$el.find("#tab-title-text-"+e);if(d){c.html(d)}return c.html()},retitle:function(d){var c=0;for(var e in this.list){this.title(e,++c+": "+d)}},_template:function(c){return'<div class="ui-tabs tabbable tabs-left"><ul id="tab-navigation" class="tab-navigation nav nav-tabs"><div class="operations" style="float: right; margin-bottom: 4px;"></div></ul><div id="tab-content" class="tab-content"/></div>'},_template_tab_new:function(c){return'<li id="new-tab"><a href="javascript:void(0);"><i class="ui-tabs-add fa fa-plus-circle"/>'+c.title_new+"</a></li>"},_template_tab:function(d){var c='<li id="tab-'+d.id+'" class="tab-element"><a id="tab-title-link-'+d.id+'" title="" href="#'+d.id+'" data-original-title=""><span id="tab-title-text-'+d.id+'" class="tab-title-text">'+d.title+"</span>";if(d.ondel){c+='<i id="delete" class="ui-tabs-delete fa fa-minus-circle"/>'}c+="</a></li>";return c},_template_tab_content:function(c){return'<div id="'+c.id+'" class="tab-pane"/>'}});return{View:b}});
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: History panel: make search input shown by default, remove toggle; List panel: make collection-panel and history-panel inherit from list-panel, move list-panel and base-mvc.list-item to mvc/list; general history and collection panel refactoring
by commits-noreply@bitbucket.org 08 Sep '14
by commits-noreply@bitbucket.org 08 Sep '14
08 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/b0c8da2b3c1e/
Changeset: b0c8da2b3c1e
User: carlfeberhard
Date: 2014-09-08 22:49:30
Summary: History panel: make search input shown by default, remove toggle; List panel: make collection-panel and history-panel inherit from list-panel, move list-panel and base-mvc.list-item to mvc/list; general history and collection panel refactoring
Affected #: 48 files
diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/base-mvc.js
--- a/static/scripts/mvc/base-mvc.js
+++ b/static/scripts/mvc/base-mvc.js
@@ -42,6 +42,7 @@
if( this.logger ){
var log = this.logger.log;
if( typeof this.logger.log === 'object' ){
+//TODO:! there has to be a way to get the lineno/file into this
log = Function.prototype.bind.call( this.logger.log, this.logger );
}
return log.apply( this.logger, arguments );
@@ -149,6 +150,23 @@
//==============================================================================
+/** Function that allows mixing of hashs into bbone MVC while showing the mixins first
+ * (before the more local class overrides/hash).
+ * Basically, a simple reversal of param order on _.defaults() - to show mixins in top of definition.
+ * @example:
+ * var NewModel = Something.extend( mixin( MyMixinA, MyMixinB, { ... myVars : ... }) );
+ *
+ * NOTE: this does not combine any hashes (like events, etc.) and you're expected to handle that
+ */
+function mixin( mixinHash1, /* mixinHash2, etc: ... variadic */ propsHash ){
+ var args = Array.prototype.slice.call( arguments, 0 ),
+ lastArg = args.pop();
+ args.unshift( lastArg );
+ return _.defaults.apply( _, args );
+}
+
+
+//==============================================================================
/** A mixin for models that allow T/F/Matching to their attributes - useful when
* searching or filtering collections of models.
* @example:
@@ -329,205 +347,6 @@
//==============================================================================
-/** Function that allows mixing of hashs into bbone MVC while showing the mixins first
- * (before the more local class overrides/hash).
- * Basically, a simple reversal of param order on _.defaults() - to show mixins in top of definition.
- * @example:
- * var NewModel = Something.extend( mixin( MyMixinA, MyMixinB, { ... myVars : ... }) );
- *
- * NOTE: this does not combine any hashes (like events, etc.) and you're expected to handle that
- */
-function mixin( mixinHash1, /* mixinHash2, etc: ... variadic */ propsHash ){
- var args = Array.prototype.slice.call( arguments, 0 ),
- lastArg = args.pop();
- args.unshift( lastArg );
- return _.defaults.apply( _, args );
-}
-
-//==============================================================================
-/** Return an underscore template fn from an array of strings.
- * @param {String[]} template the template strings to compile into the underscore template fn
- * @param {String} jsonNamespace an optional namespace for the json data passed in (defaults to 'model')
- * @returns {Function} the (wrapped) underscore template fn
- * The function accepts:
- *
- * The template strings can access:
- * the json/model hash using model ("<%- model.myAttr %>) using the jsonNamespace above
- * _l: the localizer function
- * view (if passed): ostensibly, the view using the template (handy for view instance vars)
- * Because they're namespaced, undefined attributes will not throw an error.
- *
- * @example:
- * templateBler : BASE_MVC.wrapTemplate([
- * '<div class="myclass <%- mynamespace.modelClass %>">',
- * '<span><% print( _l( mynamespace.message ) ); %>:<%= view.status %></span>'
- * '</div>'
- * ], 'mynamespace' )
- *
- * Meant to be called in a View's definition in order to compile only once.
- *
- */
-function wrapTemplate( template, jsonNamespace ){
- jsonNamespace = jsonNamespace || 'model';
- var templateFn = _.template( template.join( '' ) );
- return function( json, view ){
- var templateVars = { view : view || {}, _l : _l };
- templateVars[ jsonNamespace ] = json || {};
- return templateFn( templateVars );
- };
-}
-
-//==============================================================================
-/** A view which, when first rendered, shows only summary data/attributes, but
- * can be expanded to show further details (and optionally fetch those
- * details from the server).
- */
-var ExpandableView = Backbone.View.extend( LoggableMixin ).extend({
-//TODO: Although the reasoning behind them is different, this shares a lot with HiddenUntilActivated above: combine them
- //PRECONDITION: model must have method hasDetails
- //PRECONDITION: subclasses must have templates.el and templates.details
-
- initialize : function( attributes ){
- /** are the details of this view expanded/shown or not? */
- this.expanded = attributes.expanded || false;
- //this.log( '\t expanded:', this.expanded );
- this.fxSpeed = attributes.fxSpeed || this.fxSpeed;
- },
-
- // ........................................................................ render main
- /** jq fx speed */
- fxSpeed : 'fast',
-
- /** Render this content, set up ui.
- * @param {Number or String} speed the speed of the render
- */
- render : function( speed ){
- var $newRender = this._buildNewRender();
- this._setUpBehaviors( $newRender );
- this._queueNewRender( $newRender, speed );
- return this;
- },
-
- /** Build a temp div containing the new children for the view's $el.
- * If the view is already expanded, build the details as well.
- */
- _buildNewRender : function(){
- // create a new render using a skeleton template, render title buttons, render body, and set up events, etc.
- var $newRender = $( this.templates.el( this.model.toJSON(), this ) );
- if( this.expanded ){
- this.$details( $newRender ).replaceWith( this._renderDetails().show() );
- }
- return $newRender;
- },
-
- /** Fade out the old el, swap in the new contents, then fade in.
- * @param {Number or String} speed jq speed to use for rendering effects
- * @fires rendered when rendered
- */
- _queueNewRender : function( $newRender, speed ) {
- speed = ( speed === undefined )?( this.fxSpeed ):( speed );
- var view = this;
-
- $( view ).queue( 'fx', [
- function( next ){ this.$el.fadeOut( speed, next ); },
- function( next ){
- view._swapNewRender( $newRender );
- next();
- },
- function( next ){ this.$el.fadeIn( speed, next ); },
- function( next ){
- this.trigger( 'rendered', view );
- next();
- }
- ]);
- },
-
- /** empty out the current el, move the $newRender's children in */
- _swapNewRender : function( $newRender ){
- return this.$el.empty().attr( 'class', this.className ).append( $newRender.children() );
- },
-
- /** set up js behaviors, event handlers for elements within the given container
- * @param {jQuery} $container jq object that contains the elements to process (defaults to this.$el)
- */
- _setUpBehaviors : function( $where ){
- $where = $where || this.$el;
- // set up canned behavior on children (bootstrap, popupmenus, editable_text, etc.)
- //make_popup_menus( $where );
- $where.find( '[title]' ).tooltip({ placement : 'bottom' });
- },
-
- // ......................................................................... details
- /** shortcut to details DOM (as jQ) */
- $details : function( $where ){
- $where = $where || this.$el;
- return $where.find( '.details' );
- },
-
- /** build the DOM for the details and set up behaviors on it */
- _renderDetails : function(){
- var $newDetails = $( this.templates.details( this.model.toJSON(), this ) );
- this._setUpBehaviors( $newDetails );
- return $newDetails;
- },
-
- // ......................................................................... expansion/details
- /** Show or hide the details
- * @param {Boolean} expand if true, expand; if false, collapse
- */
- toggleExpanded : function( expand ){
- expand = ( expand === undefined )?( !this.expanded ):( expand );
- if( expand ){
- this.expand();
- } else {
- this.collapse();
- }
- return this;
- },
-
- /** Render and show the full, detailed body of this view including extra data and controls.
- * note: if the model does not have detailed data, fetch that data before showing the body
- * @fires expanded when a body has been expanded
- */
- expand : function(){
- var view = this;
- return view._fetchModelDetails()
- .always(function(){
- var $newDetails = view._renderDetails();
- view.$details().replaceWith( $newDetails );
- // needs to be set after the above or the slide will not show
- view.expanded = true;
- $newDetails.slideDown( view.fxSpeed, function(){
- view.trigger( 'expanded', view );
- });
- });
- },
-
- /** Check for model details and, if none, fetch them.
- * @returns {jQuery.promise} the model.fetch.xhr if details are being fetched, an empty promise if not
- */
- _fetchModelDetails : function(){
- if( !this.model.hasDetails() ){
- return this.model.fetch();
- }
- return jQuery.when();
- },
-
- /** Hide the body/details of an HDA.
- * @fires collapsed when a body has been collapsed
- */
- collapse : function(){
- var view = this;
- view.expanded = false;
- this.$details().slideUp( view.fxSpeed, function(){
- view.trigger( 'collapsed', view );
- });
- }
-
-});
-
-
-//==============================================================================
/** Mixin for views that can be dragged and dropped
* Allows for the drag behavior to be turned on/off, setting/removing jQuery event
* handlers each time.
@@ -711,186 +530,48 @@
//==============================================================================
-/** A view that is displayed in some larger list/grid/collection.
- * Inherits from Expandable, Selectable, Draggable.
- * The DOM contains warnings, a title bar, and a series of primary action controls.
- * Primary actions are meant to be easily accessible item functions (such as delete)
- * that are rendered in the title bar.
+/** Return an underscore template fn from an array of strings.
+ * @param {String[]} template the template strings to compile into the underscore template fn
+ * @param {String} jsonNamespace an optional namespace for the json data passed in (defaults to 'model')
+ * @returns {Function} the (wrapped) underscore template fn
+ * The function accepts:
*
- * Details are rendered when the user clicks the title bar or presses enter/space when
- * the title bar is in focus.
+ * The template strings can access:
+ * the json/model hash using model ("<%- model.myAttr %>) using the jsonNamespace above
+ * _l: the localizer function
+ * view (if passed): ostensibly, the view using the template (handy for view instance vars)
+ * Because they're namespaced, undefined attributes will not throw an error.
*
- * Designed as a base class for history panel contents - but usable elsewhere (I hope).
+ * @example:
+ * templateBler : BASE_MVC.wrapTemplate([
+ * '<div class="myclass <%- mynamespace.modelClass %>">',
+ * '<span><% print( _l( mynamespace.message ) ); %>:<%= view.status %></span>'
+ * '</div>'
+ * ], 'mynamespace' )
+ *
+ * Meant to be called in a View's definition in order to compile only once.
+ *
*/
-var ListItemView = ExpandableView.extend( mixin( SelectableViewMixin, DraggableViewMixin, {
-
-//TODO: that's a little contradictory
- tagName : 'div',
- className : 'list-item',
-
- /** Set up the base class and all mixins */
- initialize : function( attributes ){
- ExpandableView.prototype.initialize.call( this, attributes );
- SelectableViewMixin.initialize.call( this, attributes );
- DraggableViewMixin.initialize.call( this, attributes );
- },
-
- // ........................................................................ rendering
- /** In this override, call methods to build warnings, titlebar and primary actions */
- _buildNewRender : function(){
- var $newRender = ExpandableView.prototype._buildNewRender.call( this );
- $newRender.find( '.warnings' ).replaceWith( this._renderWarnings() );
- $newRender.find( '.title-bar' ).replaceWith( this._renderTitleBar() );
- $newRender.find( '.primary-actions' ).append( this._renderPrimaryActions() );
- $newRender.find( '.subtitle' ).replaceWith( this._renderSubtitle() );
- return $newRender;
- },
-
- /** In this override, render the selector controls and set up dragging before the swap */
- _swapNewRender : function( $newRender ){
- ExpandableView.prototype._swapNewRender.call( this, $newRender );
- if( this.selectable ){ this.showSelector( 0 ); }
- if( this.draggable ){ this.draggableOn(); }
- return this.$el;
- },
-
- /** Render any warnings the item may need to show (e.g. "I'm deleted") */
- _renderWarnings : function(){
- var view = this,
- $warnings = $( '<div class="warnings"></div>' ),
- json = view.model.toJSON();
-//TODO:! unordered (map)
- _.each( view.templates.warnings, function( templateFn ){
- $warnings.append( $( templateFn( json, view ) ) );
- });
- return $warnings;
- },
-
- /** Render the title bar (the main/exposed SUMMARY dom element) */
- _renderTitleBar : function(){
- return $( this.templates.titleBar( this.model.toJSON(), this ) );
- },
-
- /** Return an array of jQ objects containing common/easily-accessible item controls */
- _renderPrimaryActions : function(){
- // override this
- return [];
- },
-
- /** Render the title bar (the main/exposed SUMMARY dom element) */
- _renderSubtitle : function(){
- return $( this.templates.subtitle( this.model.toJSON(), this ) );
- },
-
- // ......................................................................... events
- /** event map */
- events : {
- // expand the body when the title is clicked or when in focus and space or enter is pressed
- 'click .title-bar' : '_clickTitleBar',
- 'keydown .title-bar' : '_keyDownTitleBar',
-
- // dragging - don't work, originalEvent === null
- //'dragstart .dataset-title-bar' : 'dragStartHandler',
- //'dragend .dataset-title-bar' : 'dragEndHandler'
-
- 'click .selector' : 'toggleSelect'
- },
-
- /** expand when the title bar is clicked */
- _clickTitleBar : function( event ){
- event.stopPropagation();
- this.toggleExpanded();
- },
-
- /** expand when the title bar is in focus and enter or space is pressed */
- _keyDownTitleBar : function( event ){
- // bail (with propagation) if keydown and not space or enter
- var KEYCODE_SPACE = 32, KEYCODE_RETURN = 13;
- if( event && ( event.type === 'keydown' )
- &&( event.keyCode === KEYCODE_SPACE || event.keyCode === KEYCODE_RETURN ) ){
- this.toggleExpanded();
- event.stopPropagation();
- return false;
- }
- return true;
- },
-
- // ......................................................................... misc
- /** String representation */
- toString : function(){
- var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
- return 'ListItemView(' + modelString + ')';
- }
-}));
-
-// ............................................................................ TEMPLATES
-/** underscore templates */
-ListItemView.prototype.templates = (function(){
-//TODO: move to require text! plugin
-
- var elTemplato = wrapTemplate([
- '<div class="list-element">',
- // errors, messages, etc.
- '<div class="warnings"></div>',
-
- // multi-select checkbox
- '<div class="selector">',
- '<span class="fa fa-2x fa-square-o"></span>',
- '</div>',
- // space for title bar buttons - gen. floated to the right
- '<div class="primary-actions"></div>',
- '<div class="title-bar"></div>',
-
- // expandable area for more details
- '<div class="details"></div>',
- '</div>'
- ]);
-
- var warnings = {};
-
- var titleBarTemplate = wrapTemplate([
- // adding a tabindex here allows focusing the title bar and the use of keydown to expand the dataset display
- '<div class="title-bar clear" tabindex="0">',
-//TODO: prob. belongs in dataset-list-item
- '<span class="state-icon"></span>',
- '<div class="title">',
- '<span class="name"><%- element.name %></span>',
- '</div>',
- '<div class="subtitle"></div>',
- '</div>'
- ], 'element' );
-
- var subtitleTemplate = wrapTemplate([
- // override this
- '<div class="subtitle"></div>'
- ]);
-
- var detailsTemplate = wrapTemplate([
- // override this
- '<div class="details"></div>'
- ]);
-
- return {
- el : elTemplato,
- warnings : warnings,
- titleBar : titleBarTemplate,
- subtitle : subtitleTemplate,
- details : detailsTemplate
+function wrapTemplate( template, jsonNamespace ){
+ jsonNamespace = jsonNamespace || 'model';
+ var templateFn = _.template( template.join( '' ) );
+ return function( json, view ){
+ var templateVars = { view : view || {}, _l : _l };
+ templateVars[ jsonNamespace ] = json || {};
+ return templateFn( templateVars );
};
-}());
+}
//==============================================================================
return {
LoggableMixin : LoggableMixin,
SessionStorageModel : SessionStorageModel,
+ mixin : mixin,
SearchableModelMixin : SearchableModelMixin,
HiddenUntilActivatedViewMixin : HiddenUntilActivatedViewMixin,
- mixin : mixin,
- wrapTemplate : wrapTemplate,
- ExpandableView : ExpandableView,
DraggableViewMixin : DraggableViewMixin,
SelectableViewMixin : SelectableViewMixin,
- ListItemView : ListItemView
+ wrapTemplate : wrapTemplate
};
});
diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/collection/collection-li.js
--- a/static/scripts/mvc/collection/collection-li.js
+++ b/static/scripts/mvc/collection/collection-li.js
@@ -1,11 +1,12 @@
define([
+ "mvc/list/list-item",
"mvc/dataset/dataset-li",
"mvc/base-mvc",
"utils/localization"
-], function( DATASET_LI, BASE_MVC, _l ){
+], function( LIST_ITEM, DATASET_LI, BASE_MVC, _l ){
/* global Backbone, LoggableMixin */
//==============================================================================
-var ListItemView = BASE_MVC.ListItemView;
+var ListItemView = LIST_ITEM.ListItemView;
/** @class Read only view for DatasetCollection.
*/
var DCListItemView = ListItemView.extend(
diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/collection/collection-model.js
--- a/static/scripts/mvc/collection/collection-model.js
+++ b/static/scripts/mvc/collection/collection-model.js
@@ -270,6 +270,11 @@
return this.save( { deleted: false }, options );
},
+ /** Is this collection deleted or purged? */
+ isDeletedOrPurged : function(){
+ return ( this.get( 'deleted' ) || this.get( 'purged' ) );
+ },
+
// ........................................................................ searchable
/** searchable attributes for collections */
searchAttributes : [
diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/collection/collection-panel.js
--- a/static/scripts/mvc/collection/collection-panel.js
+++ b/static/scripts/mvc/collection/collection-panel.js
@@ -1,9 +1,10 @@
define([
+ "mvc/list/list-panel",
"mvc/collection/collection-model",
"mvc/collection/collection-li",
"mvc/base-mvc",
"utils/localization"
-], function( DC_MODEL, DC_LI, BASE_MVC, _l ){
+], function( LIST_PANEL, DC_MODEL, DC_LI, BASE_MVC, _l ){
/* =============================================================================
TODO:
@@ -11,224 +12,81 @@
// =============================================================================
/** @class non-editable, read-only View/Controller for a dataset collection.
*/
-var CollectionPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(
+var _super = LIST_PANEL.ModelListPanel;
+var CollectionPanel = _super.extend(
/** @lends CollectionPanel.prototype */{
//MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)
/** logger used to record this.log messages, commonly set to console */
- //logger : console,
+ logger : console,
- tagName : 'div',
- className : 'dataset-collection-panel',
-
- /** (in ms) that jquery effects will use */
- fxSpeed : 'fast',
+ className : _super.prototype.className + ' dataset-collection-panel',
/** sub view class used for datasets */
DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,
/** sub view class used for nested collections */
NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView,
+ /** key of attribute in model to assign to this.collection */
+ modelCollectionKey : 'elements',
// ......................................................................... SET UP
/** Set up the view, set up storage, bind listeners to HistoryContents events
* @param {Object} attributes optional settings for the panel
*/
initialize : function( attributes ){
- attributes = attributes || {};
- // set the logger if requested
- if( attributes.logger ){
- this.logger = attributes.logger;
- }
- this.log( this + '.initialize:', attributes );
-
+ _super.prototype.initialize.call( this, attributes );
this.linkTarget = attributes.linkTarget || '_blank';
this.hasUser = attributes.hasUser;
this.panelStack = [];
this.parentName = attributes.parentName;
- //window.collectionPanel = this;
- },
-
- /** create any event listeners for the panel
- * @fires: rendered:initial on the first render
- * @fires: empty-history when switching to a history with no HDAs or creating a new history
- */
- _setUpListeners : function(){
- // debugging
- //if( this.logger ){
- this.on( 'all', function( event ){
- this.log( this + '', arguments );
- }, this );
- //}
- return this;
- },
-
- // ------------------------------------------------------------------------ history/hda event listening
- /** listening for history and HDA events */
- _setUpModelEventHandlers : function(){
- return this;
- },
-
- // ------------------------------------------------------------------------ panel rendering
- /** Render panel
- * @fires: rendered when the panel is attached and fully visible
- * @see Backbone.View#render
- */
- render : function( speed, callback ){
- this.log( 'render:', speed, callback );
- // send a speed of 0 to have no fade in/out performed
- speed = ( speed === undefined )?( this.fxSpeed ):( speed );
- //this.debug( this + '.render, fxSpeed:', speed );
- var panel = this,
- $newRender;
-
- // handle the possibility of no model (can occur if fetching the model returns an error)
- if( !this.model ){
- return this;
- }
- $newRender = this.renderModel();
-
- // fade out existing, swap with the new, fade in, set up behaviours
- $( panel ).queue( 'fx', [
- function( next ){
- if( speed && panel.$el.is( ':visible' ) ){
- panel.$el.fadeOut( speed, next );
- } else {
- next();
- }
- },
- function( next ){
- // swap over from temp div newRender
- panel.$el.empty();
- if( $newRender ){
- panel.$el.append( $newRender.children() );
- }
- next();
- },
- function( next ){
- if( speed && !panel.$el.is( ':visible' ) ){
- panel.$el.fadeIn( speed, next );
- } else {
- next();
- }
- },
- function( next ){
- //TODO: ideally, these would be set up before the fade in (can't because of async save text)
- if( callback ){ callback.call( this ); }
- panel.trigger( 'rendered', this );
- next();
- }
- ]);
- return this;
- },
-
- /** render with collection data
- * @returns {jQuery} dom fragment as temporary container to be swapped out later
- */
- renderModel : function( ){
- // tmp div for final swap in render
-//TODO: ugh - reuse issue - refactor out
- var type = this.model.get( 'collection_type' ) || this.model.object.get( 'collection_type' ),
- json = _.extend( this.model.toJSON(), {
- parentName : this.parentName,
- type : type
- }),
- $newRender = $( '<div/>' ).append( this.templates.panel( json ) );
- this._setUpBehaviours( $newRender );
- this.renderContents( $newRender );
- return $newRender;
- },
-
- /** Set up js/widget behaviours */
- _setUpBehaviours : function( $where ){
- //TODO: these should be either sub-MVs, or handled by events
- $where = $where || this.$el;
- $where.find( '[title]' ).tooltip({ placement: 'bottom' });
- return this;
- },
-
- // ------------------------------------------------------------------------ sub-$element shortcuts
- /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */
- $container : function(){
- return ( this.findContainerFn )?( this.findContainerFn.call( this ) ):( this.$el.parent() );
- },
- /** where list content views are attached */
- $datasetsList : function( $where ){
- return ( $where || this.$el ).find( '.datasets-list' );
+ window.collectionPanel = this;
},
// ------------------------------------------------------------------------ sub-views
- /** Set up/render a view for each DCE to be shown, init with model and listeners.
- * DCE views are cached to the map this.contentViews (using the model.id as key).
- * @param {jQuery} $whereTo what dom element to prepend the DCE views to
- * @returns the number of visible DCE views
- */
- renderContents : function( $whereTo ){
- //this.debug( 'renderContents, elements:', this.model.elements );
- $whereTo = $whereTo || this.$el;
-
- this.warn( this + '.renderContents:, model:', this.model );
- var panel = this,
- contentViews = {},
- //NOTE: no filtering here
- visibleContents = this.model.getVisibleContents();
- //this.debug( 'renderContents, visibleContents:', visibleContents, $whereTo );
-
- this.$datasetsList( $whereTo ).empty();
- if( visibleContents && visibleContents.length ){
- visibleContents.each( function( content ){
- var contentId = content.id,
- contentView = panel._createContentView( content );
- contentViews[ contentId ] = contentView;
- panel._attachContentView( contentView.render(), $whereTo );
- });
- }
- this.contentViews = contentViews;
- return this.contentViews;
+ /** In this override, use model.getVisibleContents */
+ _filterCollection : function(){
+//TODO: should *not* be model.getVisibleContents
+ return this.model.getVisibleContents();
},
- /** */
- _createContentView : function( content ){
- //this.debug( 'content json:', JSON.stringify( content, null, ' ' ) );
- var contentView = null,
- ContentClass = this._getContentClass( content );
- //this.debug( 'content:', content );
- //this.debug( 'ContentClass:', ContentClass );
- contentView = new ContentClass({
- model : content,
- linkTarget : this.linkTarget,
- //draggable : true,
- hasUser : this.hasUser,
- logger : this.logger
- });
- //this.debug( 'contentView:', contentView );
- this._setUpContentListeners( contentView );
- return contentView;
- },
-
- /** */
- _getContentClass : function( content ){
- //this.debug( this + '._getContentClass:', content );
+ /** override to return proper view class based on element_type */
+ _getItemViewClass : function( model ){
+ //this.debug( this + '._getItemViewClass:', model );
//TODO: subclasses use DCEViewClass - but are currently unused - decide
- switch( content.get( 'element_type' ) ){
+ switch( model.get( 'element_type' ) ){
case 'hda':
return this.DatasetDCEViewClass;
case 'dataset_collection':
return this.NestedDCDCEViewClass;
}
- throw new TypeError( 'Unknown element type:', content.get( 'element_type' ) );
+ throw new TypeError( 'Unknown element type:', model.get( 'element_type' ) );
},
- /** Set up listeners for content view events. In this override, handle collection expansion. */
- _setUpContentListeners : function( contentView ){
+ /** override to add link target and anon */
+ _getItemViewOptions : function( model ){
+ var options = _super.prototype._getItemViewOptions.call( this, model );
+ return _.extend( options, {
+ linkTarget : this.linkTarget,
+ hasUser : this.hasUser
+ });
+ },
+
+ /** when a sub-view is clicked in the collection panel that is itself a collection,
+ * hide this panel's elements and show the sub-collection in its own panel.
+ */
+ _setUpItemViewListeners : function( view ){
var panel = this;
- if( contentView.model.get( 'element_type' ) === 'dataset_collection' ){
- contentView.on( 'expanded', function( collectionView ){
+ _super.prototype._setUpItemViewListeners.call( panel, view );
+ //TODO:?? doesn't seem to belong here
+ if( view.model.get( 'element_type' ) === 'dataset_collection' ){
+ view.on( 'expanded', function( collectionView ){
panel.info( 'expanded', collectionView );
panel._addCollectionPanel( collectionView );
});
}
+ return panel;
},
/** When a sub-collection is clicked, hide the current panel and render the sub-collection in its own panel */
@@ -246,7 +104,7 @@
});
currPanel.panelStack.push( panel );
- currPanel.$( '.controls' ).add( '.datasets-list' ).hide();
+ currPanel.$( '.controls' ).add( '.list-items' ).hide();
currPanel.$el.append( panel.$el );
panel.on( 'close', function(){
currPanel.render();
@@ -266,14 +124,6 @@
}
},
- /** attach an contentView to the panel */
- _attachContentView : function( contentView, $whereTo ){
- $whereTo = $whereTo || this.$el;
- var $datasetsList = this.$datasetsList( $whereTo );
- $datasetsList.append( contentView.$el );
- return this;
- },
-
// ------------------------------------------------------------------------ panel events
/** event map */
events : {
@@ -293,16 +143,16 @@
}
});
-//----------------------------------------------------------------------------- TEMPLATES
-/** underscore templates */
-CollectionPanel.templates = CollectionPanel.prototype.templates = (function(){
-// use closure to run underscore template fn only once at module load
- var _panelTemplate = _.template([
+
+//------------------------------------------------------------------------------ TEMPLATES
+CollectionPanel.prototype.templates = (function(){
+
+ var controlsTemplate = BASE_MVC.wrapTemplate([
'<div class="controls">',
'<div class="navigation">',
'<a class="back" href="javascript:void(0)">',
'<span class="fa fa-icon fa-angle-left"></span>',
- _l( 'Back to ' ), '<%- collection.parentName %>',
+ _l( 'Back to ' ), '<%- view.parentName %>',
'</a>',
'</div>',
@@ -310,29 +160,25 @@
'<div class="name"><%- collection.name || collection.element_identifier %></div>',
'<div class="subtitle">',
//TODO: remove logic from template
- '<% if( collection.type === "list" ){ %>',
+ '<% if( collection.collection_type === "list" ){ %>',
_l( 'a list of datasets' ),
- '<% } else if( collection.type === "paired" ){ %>',
+ '<% } else if( collection.collection_type === "paired" ){ %>',
_l( 'a pair of datasets' ),
- '<% } else if( collection.type === "list:paired" ){ %>',
+ '<% } else if( collection.collection_type === "list:paired" ){ %>',
_l( 'a list of paired datasets' ),
'<% } %>',
'</div>',
'</div>',
- '</div>',
- // where the datasets/hdas are added
- '<div class="datasets-list"></div>'
- ].join( '' ));
+ '</div>'
+ ], 'collection' );
- // we override here in order to pass the localizer (_L) into the template scope - since we use it as a fn within
- return {
- panel : function( json ){
- return _panelTemplate({ _l: _l, collection: json });
- }
- };
+ return _.extend( _.clone( _super.prototype.templates ), {
+ controls : controlsTemplate
+ });
}());
+
// =============================================================================
/** @class non-editable, read-only View/Controller for a dataset collection. */
var ListCollectionPanel = CollectionPanel.extend(
diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/dataset/dataset-li.js
--- a/static/scripts/mvc/dataset/dataset-li.js
+++ b/static/scripts/mvc/dataset/dataset-li.js
@@ -1,8 +1,9 @@
define([
+ "mvc/list/list-item",
"mvc/dataset/states",
"mvc/base-mvc",
"utils/localization"
-], function( STATES, BASE_MVC, _l ){
+], function( LIST_ITEM, STATES, BASE_MVC, _l ){
/* global Backbone */
/*==============================================================================
TODO:
@@ -11,7 +12,7 @@
simplify button rendering
==============================================================================*/
-var _super = BASE_MVC.ListItemView;
+var _super = LIST_ITEM.ListItemView;
/** @class Read only list view for either LDDAs, HDAs, or HDADCEs.
* Roughly, any DatasetInstance (and not a raw Dataset).
*/
diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/dataset/dataset-list.js
--- a/static/scripts/mvc/dataset/dataset-list.js
+++ b/static/scripts/mvc/dataset/dataset-list.js
@@ -1,5 +1,5 @@
define([
- "mvc/dataset/list-panel",
+ "mvc/list/list-panel",
"mvc/dataset/dataset-li",
"mvc/base-mvc",
"utils/localization"
diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/dataset/dataset-model.js
--- a/static/scripts/mvc/dataset/dataset-model.js
+++ b/static/scripts/mvc/dataset/dataset-model.js
@@ -94,6 +94,9 @@
this.trigger( 'state:ready', currModel, newState, this.previous( 'state' ) );
}
});
+ this.on( 'change:urls', function(){
+ console.warn( 'change:urls', arguments );
+ });
// the download url (currenlty) relies on having a correct file extension
this.on( 'change:id change:file_ext', function( currModel ){
this._generateUrls();
@@ -104,6 +107,8 @@
/** override to add urls */
toJSON : function(){
var json = Backbone.Model.prototype.toJSON.call( this );
+ //console.warn( 'returning json?' );
+ //return json;
return _.extend( json, {
urls : this.urls
});
diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/dataset/list-panel.js
--- a/static/scripts/mvc/dataset/list-panel.js
+++ /dev/null
@@ -1,687 +0,0 @@
-define([
- "mvc/base-mvc",
- "utils/localization"
-], function( BASE_MVC, _l ){
-/* =============================================================================
-TODO:
-
-============================================================================= */
-/** @class List that contains ListItemViews.
- */
-var ListPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(
-/** @lends ReadOnlyHistoryPanel.prototype */{
-
- /** logger used to record this.log messages, commonly set to console */
- //logger : console,
-
- /** class to use for constructing the sub-views */
- viewClass : BASE_MVC.ListItemView,
-
- tagName : 'div',
- className : 'list-panel',
-
- /** (in ms) that jquery effects will use */
- fxSpeed : 'fast',
-
- /** string to display when the model has no hdas */
- emptyMsg : _l( 'This list is empty' ),
- /** string to no hdas match the search terms */
- noneFoundMsg : _l( 'No matching items found' ),
-
- // ......................................................................... SET UP
- /** Set up the view, set up storage, bind listeners to HistoryContents events
- * @param {Object} attributes optional settings for the list
- */
- initialize : function( attributes, options ){
- attributes = attributes || {};
- // set the logger if requested
- if( attributes.logger ){
- this.logger = attributes.logger;
- }
- this.log( this + '.initialize:', attributes );
-
- // ---- instance vars
- /** how quickly should jquery fx run? */
- this.fxSpeed = _.has( attributes, 'fxSpeed' )?( attributes.fxSpeed ):( this.fxSpeed );
-
- /** filters for displaying subviews */
- this.filters = [];
- /** current search terms */
- this.searchFor = attributes.searchFor || '';
-
- /** loading indicator */
- this.indicator = new LoadingIndicator( this.$el );
-
- /** currently showing selectors on items? */
- this.selecting = ( attributes.selecting !== undefined )? attributes.selecting : true;
- //this.selecting = false;
-
- /** cached selected item.model.ids to persist btwn renders */
- this.selected = attributes.selected || [];
- /** the last selected item.model.id */
- this.lastSelected = null;
-
- /** list item view class (when passed models) */
- this.viewClass = attributes.viewClass || this.viewClass;
-
- /** list item views */
- this.views = [];
- /** list item models */
- this.collection = attributes.collection || ( new Backbone.Collection([]) );
-
- /** filter fns run over collection items to see if they should show in the list */
- this.filters = attributes.filters || [];
-
-//TODO: remove
- this.title = attributes.title || '';
- this.subtitle = attributes.subtitle || '';
-
- this._setUpListeners();
- },
-
- /** create any event listeners for the list
- */
- _setUpListeners : function(){
- this.on( 'error', function( model, xhr, options, msg, details ){
- //this.errorHandler( model, xhr, options, msg, details );
- console.error( model, xhr, options, msg, details );
- }, this );
-
- // show hide the loading indicator
- this.on( 'loading', function(){
- this._showLoadingIndicator( 'loading...', 40 );
- }, this );
- this.on( 'loading-done', function(){
- this._hideLoadingIndicator( 40 );
- }, this );
-
- // throw the first render up as a diff namespace using once (for outside consumption)
- this.once( 'rendered', function(){
- this.trigger( 'rendered:initial', this );
- }, this );
-
- // debugging
- if( this.logger ){
- this.on( 'all', function( event ){
- this.log( this + '', arguments );
- }, this );
- }
-
- this._setUpCollectionListeners();
- this._setUpViewListeners();
- return this;
- },
-
- /** free any sub-views the list has */
- freeViews : function(){
-//TODO: stopListening? remove?
- this.views = [];
- return this;
- },
-
- // ------------------------------------------------------------------------ item listeners
- /** listening for history and HDA events */
- _setUpCollectionListeners : function(){
-
- this.collection.on( 'reset', function(){
- this.renderItems();
- }, this );
-
- this.collection.on( 'add', this.addItemView, this );
- this.collection.on( 'remove', this.removeItemView, this );
-
- // debugging
- if( this.logger ){
- this.collection.on( 'all', function( event ){
- this.info( this + '(collection)', arguments );
- }, this );
- }
- return this;
- },
-
- /** listening for history and HDA events */
- _setUpViewListeners : function(){
-
- // shift to select a range
- this.on( 'view:selected', function( view, ev ){
- if( ev && ev.shiftKey && this.lastSelected ){
- var lastSelectedView = _.find( this.views, function( view ){
- return view.model.id === this.lastSelected;
- });
- if( lastSelectedView ){
- this.selectRange( view, lastSelectedView );
- }
- }
- this.selected.push( view.model.id );
- this.lastSelected = view.model.id;
- }, this );
- },
-
- // ------------------------------------------------------------------------ rendering
- /** Render this content, set up ui.
- * @param {Number or String} speed the speed of the render
- */
- render : function( speed ){
- var $newRender = this._buildNewRender();
- this._setUpBehaviors( $newRender );
- this._queueNewRender( $newRender, speed );
- return this;
- },
-
- /** Build a temp div containing the new children for the view's $el.
- */
- _buildNewRender : function(){
- // create a new render using a skeleton template, render title buttons, render body, and set up events, etc.
- var json = this.model? this.model.toJSON() : {},
- $newRender = $( this.templates.el( json, this ) );
- this._renderTitle( $newRender );
- this._renderSubtitle( $newRender );
- this._renderSearch( $newRender );
- this.renderItems( $newRender );
- return $newRender;
- },
-
- /**
- */
- _renderTitle : function( $where ){
- //$where = $where || this.$el;
- //$where.find( '.title' ).replaceWith( ... )
- },
-
- /**
- */
- _renderSubtitle : function( $where ){
- //$where = $where || this.$el;
- //$where.find( '.title' ).replaceWith( ... )
- },
-
- /** Fade out the old el, swap in the new contents, then fade in.
- * @param {Number or String} speed jq speed to use for rendering effects
- * @fires rendered when rendered
- */
- _queueNewRender : function( $newRender, speed ) {
- speed = ( speed === undefined )?( this.fxSpeed ):( speed );
- var view = this;
-
- $( view ).queue( 'fx', [
- function( next ){ this.$el.fadeOut( speed, next ); },
- function( next ){
- view._swapNewRender( $newRender );
- next();
- },
- function( next ){ this.$el.fadeIn( speed, next ); },
- function( next ){
- view.trigger( 'rendered', view );
- next();
- }
- ]);
- },
-
- /** empty out the current el, move the $newRender's children in */
- _swapNewRender : function( $newRender ){
- this.$el.empty().attr( 'class', this.className ).append( $newRender.children() );
- if( this.selecting ){ this.showSelectors( 0 ); }
- return this;
- },
-
- /** */
- _setUpBehaviors : function( $where ){
- $where = $where || this.$el;
- $where.find( '.controls [title]' ).tooltip({ placement: 'bottom' });
- return this;
- },
-
- // ------------------------------------------------------------------------ sub-$element shortcuts
- /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */
- $scrollContainer : function(){
- // override
- return this.$el.parent().parent();
- },
- /** */
- $list : function( $where ){
- return ( $where || this.$el ).find( '.list-items' );
- },
- /** container where list messages are attached */
- $messages : function( $where ){
- return ( $where || this.$el ).find( '.message-container' );
- },
- /** the message displayed when no views can be shown (no views, none matching search) */
- $emptyMessage : function( $where ){
- return ( $where || this.$el ).find( '.empty-message' );
- },
-
- // ------------------------------------------------------------------------ hda sub-views
- /**
- * @param {jQuery} $whereTo what dom element to prepend the HDA views to
- * @returns the visible item views
- */
- renderItems : function( $whereTo ){
- $whereTo = $whereTo || this.$el;
- var list = this,
- newViews = [];
-
- var $list = this.$list( $whereTo ),
- item$els = this._filterCollection().map( function( itemModel ){
-//TODO: creates views each time - not neccessarily good
- var view = list._createItemView( itemModel );
- newViews.push( view );
- return view.render( 0 ).$el;
- });
- this.debug( item$els );
- this.debug( newViews );
-
- $list.empty();
- if( item$els.length ){
- $list.append( item$els );
- this.$emptyMessage( $whereTo ).hide();
-
- } else {
- this._renderEmptyMessage( $whereTo ).show();
- }
-
- this.views = newViews;
- return newViews;
- },
-
- /**
- */
- _filterCollection : function(){
- // override this
- var list = this;
- return list.collection.filter( _.bind( list._filterItem, list ) );
- },
-
- /**
- */
- _filterItem : function( model ){
- // override this
- var list = this;
- return ( _.every( list.filters.map( function( fn ){ return fn.call( model ); }) ) )
- && ( !list.searchFor || model.matchesAll( list.searchFor ) );
- },
-
- /**
- */
- _createItemView : function( model ){
- var ViewClass = this._getItemViewClass( model ),
- options = _.extend( this._getItemViewOptions( model ), {
- model : model
- }),
- view = new ViewClass( options );
- this._setUpItemViewListeners( view );
- return view;
- },
-
- _getItemViewClass : function( model ){
- // override this
- return this.viewClass;
- },
-
- _getItemViewOptions : function( model ){
- // override this
- return {
- //logger : this.logger,
- fxSpeed : this.fxSpeed,
- expanded : false,
- selectable : this.selecting,
- selected : _.contains( this.selected, model.id ),
- draggable : this.dragging
- };
- },
-
- /**
- */
- _setUpItemViewListeners : function( view ){
- var list = this;
- view.on( 'all', function(){
- var args = Array.prototype.slice.call( arguments, 0 );
- args[0] = 'view:' + args[0];
- list.trigger.apply( list, args );
- });
-
- // debugging
- //if( this.logger ){
- // view.on( 'all', function( event ){
- // this.log( this + '(view)', arguments );
- // }, this );
- //}
- return this;
- },
-
- /** render the empty/none-found message */
- _renderEmptyMessage : function( $whereTo ){
- //this.debug( '_renderEmptyMessage', $whereTo, this.searchFor );
- var text = this.searchFor? this.noneFoundMsg : this.emptyMsg;
- return this.$emptyMessage( $whereTo ).text( text );
- },
-
- /** collapse all item views */
- expandAll : function(){
- _.each( this.views, function( view ){
- view.expand();
- });
- },
-
- /** collapse all item views */
- collapseAll : function(){
- _.each( this.views, function( view ){
- view.collapse();
- });
- },
-
- // ------------------------------------------------------------------------ collection/views syncing
- /**
- */
- addItemView : function( model, collection, options ){
- this.log( this + '.addItemView:', model );
- var list = this;
- if( !this._filterItem( model ) ){ return undefined; }
-
-//TODO: sorted? position?
- var view = list._createItemView( model );
- this.views.push( view );
-
- $( view ).queue( 'fx', [
- function( next ){ list.$emptyMessage().fadeOut( list.fxSpeed, next ); },
- function( next ){
-//TODO: auto render?
- list.$list().append( view.render().$el );
- next();
- }
- ]);
- return view;
- },
-
- /**
- */
- removeItemView : function( model, collection, options ){
- this.log( this + '.removeItemView:', model );
- var list = this,
- view = list.viewFromModel( model );
- if( !view ){ return undefined; }
-
- this.views = _.without( this.views, view );
- view.remove();
- if( !this.views.length ){
- list._renderEmptyMessage().fadeIn( list.fxSpeed );
- }
- return view;
- },
-
- /** get views based on model
- */
- viewFromModel : function( model ){
- for( var i=0; i<this.views.length; i++ ){
- var view = this.views[i];
- if( view.model === model ){
- return view;
- }
- }
- return undefined;
- },
-
- /** get views based on model properties
- */
- viewsWhereModel : function( properties ){
- return this.views.filter( function( view ){
- //return view.model.matches( properties );
-//TODO: replace with _.matches (underscore 1.6.0)
- var json = view.model.toJSON();
- //console.debug( '\t', json, properties );
- for( var key in properties ){
- if( properties.hasOwnPropery( key ) ){
- //console.debug( '\t\t', json[ key ], view.model.properties[ key ] );
- if( json[ key ] !== view.model.properties[ key ] ){
- return false;
- }
- }
- }
- return true;
- });
- },
-
- /**
- */
- viewRange : function( viewA, viewB ){
- if( viewA === viewB ){ return ( viewA )?( [ viewA ] ):( [] ); }
-
- var indexA = this.views.indexOf( viewA ),
- indexB = this.views.indexOf( viewB );
-
- // handle not found
- if( indexA === -1 || indexB === -1 ){
- if( indexA === indexB ){ return []; }
- return ( indexA === -1 )?( [ viewB ] ):( [ viewA ] );
- }
- // reverse if indeces are
- //note: end inclusive
- return ( indexA < indexB )?
- this.views.slice( indexA, indexB + 1 ) :
- this.views.slice( indexB, indexA + 1 );
- },
-
- // ------------------------------------------------------------------------ searching
- /** render a search input for filtering datasets shown
- * (see the search section in the HDA model for implementation of the actual searching)
- * return will start the search
- * esc will clear the search
- * clicking the clear button will clear the search
- * uses searchInput in ui.js
- */
- _renderSearch : function( $where ){
- $where.find( '.controls .search-input' ).searchInput({
- placeholder : 'search',
- initialVal : this.searchFor,
- onfirstsearch : _.bind( this._firstSearch, this ),
- onsearch : _.bind( this.searchItems, this ),
- onclear : _.bind( this.clearSearch, this )
- });
- return $where;
- },
-
- _firstSearch : function( searchFor ){
- this.log( 'onFirstSearch', searchFor );
- return this.searchItems( searchFor );
- },
-
- /** filter view list to those that contain the searchFor terms */
- searchItems : function( searchFor ){
- this.searchFor = searchFor;
- this.trigger( 'search:searching', searchFor, this );
- this.renderItems();
- return this;
- },
-
- /** clear the search filters and show all views that are normally shown */
- clearSearch : function( searchFor ){
- //this.log( 'onSearchClear', this );
- this.searchFor = '';
- this.trigger( 'search:clear', this );
- this.renderItems();
- return this;
- },
-
- // ------------------------------------------------------------------------ selection
- /** show selectors on all visible hdas and associated controls */
- showSelectors : function( speed ){
- speed = ( speed !== undefined )?( speed ):( this.fxSpeed );
- this.selecting = true;
- this.$( '.list-actions' ).slideDown( speed );
- _.each( this.views, function( view ){
- view.showSelector( speed );
- });
- this.selected = [];
- this.lastSelected = null;
- },
-
- /** hide selectors on all visible hdas and associated controls */
- hideSelectors : function( speed ){
- speed = ( speed !== undefined )?( speed ):( this.fxSpeed );
- this.selecting = false;
- this.$( '.list-actions' ).slideUp( speed );
- _.each( this.views, function( view ){
- view.hideSelector( speed );
- });
- this.selected = [];
- this.lastSelected = null;
- },
-
- /** show or hide selectors on all visible hdas and associated controls */
- toggleSelectors : function(){
- if( !this.selecting ){
- this.showSelectors();
- } else {
- this.hideSelectors();
- }
- },
-
- /** select all visible hdas */
- selectAll : function( event ){
- _.each( this.views, function( view ){
- view.select( event );
- });
- },
-
- /** deselect all visible hdas */
- deselectAll : function( event ){
- this.lastSelected = null;
- _.each( this.views, function( view ){
- view.deselect( event );
- });
- },
-
- /** select a range of datasets between A and B */
- selectRange : function( viewA, viewB ){
- var range = this.viewRange( viewA, viewB );
- _.each( range, function( view ){
- view.select();
- });
- return range;
- },
-
- /** return an array of all currently selected hdas */
- getSelectedViews : function(){
- return _.filter( this.views, function( v ){
- return v.selected;
- });
- },
-
- /** return an collection of the models of all currenly selected hdas */
- getSelectedModels : function(){
- return new this.collection.constructor( _.map( this.getSelectedViews(), function( view ){
- return view.model;
- }));
- },
-
- // ------------------------------------------------------------------------ loading indicator
-//TODO: questionable
- /** hide the $el and display a loading indicator (in the $el's parent) when loading new data */
- _showLoadingIndicator : function( msg, speed, callback ){
- speed = ( speed !== undefined )?( speed ):( this.fxSpeed );
- if( !this.indicator ){
- this.indicator = new LoadingIndicator( this.$el, this.$el.parent() );
- }
- if( !this.$el.is( ':visible' ) ){
- this.indicator.show( 0, callback );
- } else {
- this.$el.fadeOut( speed );
- this.indicator.show( msg, speed, callback );
- }
- },
-
- /** hide the loading indicator */
- _hideLoadingIndicator : function( speed, callback ){
- speed = ( speed !== undefined )?( speed ):( this.fxSpeed );
- if( this.indicator ){
- this.indicator.hide( speed, callback );
- }
- },
-
- // ------------------------------------------------------------------------ scrolling
- /** get the current scroll position of the panel in its parent */
- scrollPosition : function(){
- return this.$scrollContainer().scrollTop();
- },
-
- /** set the current scroll position of the panel in its parent */
- scrollTo : function( pos ){
- this.$scrollContainer().scrollTop( pos );
- return this;
- },
-
- /** Scrolls the panel to the top. */
- scrollToTop : function(){
- this.$scrollContainer().scrollTop( 0 );
- return this;
- },
-
- /** */
- scrollToItem : function( view ){
- if( !view ){ return; }
- var itemTop = view.$el.offset().top;
- this.$scrollContainer().scrollTop( itemTop );
- },
-
- // ------------------------------------------------------------------------ panel events
- /** event map */
- events : {
- 'click .select-all' : 'selectAll',
- 'click .deselect-all' : 'deselectAll'
- },
-
- // ------------------------------------------------------------------------ misc
- /** Return a string rep of the history */
- toString : function(){
- return 'ListPanel(' + this.collection + ')';
- }
-});
-
-// ............................................................................ TEMPLATES
-/** underscore templates */
-ListPanel.prototype.templates = (function(){
-//TODO: move to require text! plugin
-
- var elTemplate = BASE_MVC.wrapTemplate([
- // temp container
- '<div>',
- '<div class="controls">',
- '<div class="title">',
- '<div class="name"><%= model.name || view.title %></div>',
- '</div>',
- '<div class="subtitle"><%= view.subtitle %></div>',
- '<div class="actions"></div>',
- '<div class="messages"></div>',
-
- '<div class="search">',
- '<div class="search-input"></div>',
- '</div>',
-
- '<div class="list-actions">',
- '<div class="btn-group">',
- '<button class="select-all btn btn-default"',
- 'data-mode="select">', _l( 'All' ), '</button>',
- '<button class="deselect-all btn btn-default"',
- 'data-mode="select">', _l( 'None' ), '</button>',
- '</div>',
- //'<button class="action-popup-btn btn btn-default">',
- // _l( 'For all selected' ), '...',
- //'</button>',
- '</div>',
- '</div>',
- '<div class="list-items"></div>',
- '<div class="empty-message infomessagesmall"></div>',
- '</div>'
- ]);
-
- return {
- el : elTemplate
- };
-}());
-
-
-
-//==============================================================================
- return {
- ListPanel: ListPanel
- };
-});
diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/history/hda-li.js
--- a/static/scripts/mvc/history/hda-li.js
+++ b/static/scripts/mvc/history/hda-li.js
@@ -17,12 +17,12 @@
/** logger used to record this.log messages, commonly set to console */
//logger : console,
+ className : _super.prototype.className + " history-content",
+
initialize : function( attributes, options ){
_super.prototype.initialize.call( this, attributes, options );
},
- className : _super.prototype.className + " history-content",
-
// ......................................................................... misc
/** String representation */
toString : function(){
diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/history/history-contents.js
--- a/static/scripts/mvc/history/history-contents.js
+++ b/static/scripts/mvc/history/history-contents.js
@@ -53,6 +53,7 @@
*/
initialize : function( models, options ){
options = options || {};
+//TODO: could probably use the contents.history_id instead
this.historyId = options.historyId;
//this._setUpListeners();
@@ -226,7 +227,7 @@
if( !existing ){ return model; }
// merge the models _BEFORE_ calling the superclass version
- var merged = existing.toJSON();
+ var merged = _.clone( existing.attributes );
_.extend( merged, model );
return merged;
});
@@ -303,6 +304,13 @@
return xhr;
},
+ /** In this override, copy the historyId to the clone */
+ clone : function(){
+ var clone = Backbone.Collection.prototype.clone.call( this );
+ clone.historyId = this.historyId;
+ return clone;
+ },
+
/** debugging */
print : function(){
var contents = this;
diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/history/history-model.js
--- a/static/scripts/mvc/history/history-model.js
+++ b/static/scripts/mvc/history/history-model.js
@@ -232,7 +232,7 @@
/** Get data for a history then its hdas using a sequential ajax call, return a deferred to receive both */
History.getHistoryData = function getHistoryData( historyId, options ){
options = options || {};
- var hdaDetailIds = options.hdaDetailIds || [];
+ var detailIdsFn = options.detailIdsFn || [];
var hdcaDetailIds = options.hdcaDetailIds || [];
//console.debug( 'getHistoryData:', historyId, options );
@@ -250,20 +250,20 @@
// get the number of hdas accrd. to the history
return historyData && historyData.empty;
}
- function getHdas( historyData ){
+ function getContents( historyData ){
// get the hda data
// if no hdas accrd. to history: return empty immed.
if( isEmpty( historyData ) ){ return []; }
// if there are hdas accrd. to history: get those as well
- if( _.isFunction( hdaDetailIds ) ){
- hdaDetailIds = hdaDetailIds( historyData );
+ if( _.isFunction( detailIdsFn ) ){
+ detailIdsFn = detailIdsFn( historyData );
}
if( _.isFunction( hdcaDetailIds ) ){
hdcaDetailIds = hdcaDetailIds( historyData );
}
var data = {};
- if( hdaDetailIds.length ) {
- data.dataset_details = hdaDetailIds.join( ',' );
+ if( detailIdsFn.length ) {
+ data.dataset_details = detailIdsFn.join( ',' );
}
if( hdcaDetailIds.length ) {
// for symmetry, not actually used by backend of consumed
@@ -274,10 +274,10 @@
}
// getting these concurrently is 400% slower (sqlite, local, vanilla) - so:
- // chain the api calls - getting history first then hdas
+ // chain the api calls - getting history first then contents
var historyFn = options.historyFn || getHistory,
- hdaFn = options.hdaFn || getHdas;
+ contentsFn = options.contentsFn || getContents;
// chain ajax calls: get history first, then hdas
var historyXHR = historyFn( historyId );
@@ -291,15 +291,15 @@
df.reject( xhr, 'loading the history' );
});
- var hdaXHR = historyXHR.then( hdaFn );
- hdaXHR.then( function( hdaJSON ){
- df.notify({ status: 'dataset data retrieved', historyJSON: historyJSON, hdaJSON: hdaJSON });
+ var contentsXHR = historyXHR.then( contentsFn );
+ contentsXHR.then( function( contentsJSON ){
+ df.notify({ status: 'contents data retrieved', historyJSON: historyJSON, contentsJSON: contentsJSON });
// we've got both: resolve the outer scope deferred
- df.resolve( historyJSON, hdaJSON );
+ df.resolve( historyJSON, contentsJSON );
});
- hdaXHR.fail( function( xhr, status, message ){
+ contentsXHR.fail( function( xhr, status, message ){
// call reject on the outer deferred to allow its fail callback to run
- df.reject( xhr, 'loading the datasets', { history: historyJSON } );
+ df.reject( xhr, 'loading the contents', { history: historyJSON } );
});
return df;
diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/history/history-panel-annotated.js
--- a/static/scripts/mvc/history/history-panel-annotated.js
+++ b/static/scripts/mvc/history/history-panel-annotated.js
@@ -1,118 +1,97 @@
define([
"mvc/history/history-panel",
"mvc/history/hda-li",
+ "mvc/history/hdca-li",
+ "mvc/base-mvc",
"utils/localization"
-], function( HPANEL, HDA_LI, _l ){
+], function( HPANEL, HDA_LI, HDCA_LI, BASE_MVC, _l ){
/* =============================================================================
TODO:
============================================================================= */
-var _super = HPANEL.ReadOnlyHistoryPanel;
+var _super = HPANEL.HistoryPanel;
// used in history/display.mako and history/embed.mako
/** @class View/Controller for a tabular view of the history model.
- * @name AnnotatedHistoryPanel
*
* As ReadOnlyHistoryPanel, but with:
* history annotation always shown
* datasets displayed in a table:
* datasets in left cells, dataset annotations in the right
- *
- * @augments Backbone.View
- * @borrows LoggableMixin#logger as #logger
- * @borrows LoggableMixin#log as #log
- * @constructs
*/
var AnnotatedHistoryPanel = _super.extend(
/** @lends AnnotatedHistoryPanel.prototype */{
/** logger used to record this.log messages, commonly set to console */
- // comment this out to suppress log output
//logger : console,
- className : 'annotated-history-panel',
-
- //TODO:?? possibly into own annotated class
- /** class to use for constructing the HDA views */
- //HDAViewClass : HDA_LI.HDAListItemView,
+ className : _super.prototype.className + ' annotated-history-panel',
// ------------------------------------------------------------------------ panel rendering
- /** render with history data
- * In this override:
- * replace the datasets list with a table,
- * add the history annotation,
- * and move the search controls
- * @returns {jQuery} dom fragment as temporary container to be swapped out later
+ /** In this override, add the history annotation
*/
- renderModel : function( ){
- // why do we need this here? why isn't className being applied?
- this.$el.addClass( this.className );
- var $newRender = _super.prototype.renderModel.call( this ),
- // move datasets from div to table
- $datasetsList = this.$datasetsList( $newRender ),
- $datasetsTable = $( '<table/>' ).addClass( 'datasets-list datasets-table' );
- $datasetsTable.append( $datasetsList.children() );
- $datasetsList.replaceWith( $datasetsTable );
- //TODO: it's possible to do this with css only, right? display: table-cell, etc.?
-
- // add history annotation under subtitle
- $newRender.find( '.history-subtitle' ).after( this.renderHistoryAnnotation() );
-
- // hide search button, move search bar beneath controls (instead of above title), show, and set up
- $newRender.find( '.history-search-btn' ).hide();
- $newRender.find( '.history-controls' ).after( $newRender.find( '.history-search-controls' ).show() );
-
+ _buildNewRender : function(){
+ //TODO: shouldn't this display regardless (on all non-current panels)?
+ var $newRender = _super.prototype._buildNewRender.call( this );
+ this.renderHistoryAnnotation( $newRender );
return $newRender;
},
/** render the history's annotation as its own field */
- renderHistoryAnnotation : function(){
+ renderHistoryAnnotation : function( $newRender ){
var annotation = this.model.get( 'annotation' );
- if( !annotation ){ return null; }
- return $([
- '<div class="history-annotation">', annotation, '</div>'
- ].join( '' ));
+ if( !annotation ){ return; }
+ $newRender.find( '.controls .annotation-display' ).text( annotation );
},
- /** Set up/render a view for each HDA to be shown, init with model and listeners.
- * In this override, add table header cells to indicate the dataset, annotation columns
+ /** In this override, convert the list-items tag to a table
+ * and add table header cells to indicate the dataset, annotation columns
*/
- renderHdas : function( $whereTo ){
+ renderItems : function( $whereTo ){
$whereTo = $whereTo || this.$el;
- var hdaViews = _super.prototype.renderHdas.call( this, $whereTo );
- this.$datasetsList( $whereTo ).prepend( $( '<tr/>' ).addClass( 'headers' ).append([
- $( '<th/>' ).text( _l( 'Dataset' ) ),
- $( '<th/>' ).text( _l( 'Annotation' ) )
- ]));
- return hdaViews;
+
+ // convert to table
+ $whereTo.find( '.list-items' )
+ .replaceWith( $( '<table/>' ).addClass( 'list-items' ) );
+
+ // render rows/contents and prepend headers
+ var views = _super.prototype.renderItems.call( this, $whereTo );
+ this.$list( $whereTo )
+ .prepend( $( '<tr/>' ).addClass( 'headers' ).append([
+ $( '<th/>' ).text( _l( 'Dataset' ) ),
+ $( '<th/>' ).text( _l( 'Annotation' ) )
+ ]));
+ return views;
},
- // ------------------------------------------------------------------------ hda sub-views
- /** attach an hdaView to the panel
- * In this override, wrap the hdaView in a table row and cell, adding a 2nd cell for the hda annotation
+ // ------------------------------------------------------------------------ sub-views
+ /** In this override, wrap the content view in a table row
+ * with the content in the left td and annotation/extra-info in the right
*/
- attachContentView : function( hdaView, $whereTo ){
- $whereTo = $whereTo || this.$el;
- // build a row around the dataset with the std hdaView in the first cell and the annotation in the next
- var stateClass = _.find( hdaView.el.classList, function( c ){ return ( /^state\-/ ).test( c ); }),
- annotation = hdaView.model.get( 'annotation' ) || '',
- $tr = $( '<tr/>' ).addClass( 'dataset-row' ).append([
- $( '<td/>' ).addClass( 'dataset-container' ).append( hdaView.$el )
- // visually match the cell bg to the dataset at runtime (prevents the empty space)
- // (getting bg via jq on hidden elem doesn't work on chrome/webkit - so use states)
- //.css( 'background-color', hdaView.$el.css( 'background-color' ) ),
- .addClass( stateClass? stateClass.replace( '-', '-color-' ): '' ),
- $( '<td/>' ).addClass( 'additional-info' ).text( annotation )
- ]);
- this.$datasetsList( $whereTo ).append( $tr );
+ _attachItems : function( $whereTo ){
+ this.$list( $whereTo ).append( this.views.map( function( view ){
+ //TODO:?? possibly make this more flexible: instead of annotation use this._additionalInfo()
+ // build a row around the dataset with the std itemView in the first cell and the annotation in the next
+ var stateClass = _.find( view.el.classList, function( c ){ return ( /^state\-/ ).test( c ); }),
+ annotation = view.model.get( 'annotation' ) || '',
+ $tr = $( '<tr/>' ).append([
+ $( '<td/>' ).addClass( 'contents-container' ).append( view.$el )
+ // visually match the cell bg to the dataset at runtime (prevents the empty space)
+ // (getting bg via jq on hidden elem doesn't work on chrome/webkit - so use states)
+ //.css( 'background-color', view.$el.css( 'background-color' ) ),
+ .addClass( stateClass? stateClass.replace( '-', '-color-' ): '' ),
+ $( '<td/>' ).addClass( 'additional-info' ).text( annotation )
+ ]);
+ return $tr;
+ }));
+ return this;
},
// ------------------------------------------------------------------------ panel events
/** event map */
events : _.extend( _.clone( _super.prototype.events ), {
+ // clicking on any part of the row will expand the items
'click tr' : function( ev ){
- //if( !ev.target.hasAttribute( 'href' ) ){
- $( ev.currentTarget ).find( '.dataset-title-bar' ).click();
- //}
+ $( ev.currentTarget ).find( '.title-bar' ).click();
},
// prevent propagation on icon btns so they won't bubble up to tr and toggleBodyVisibility
'click .icon-btn' : function( ev ){
@@ -127,6 +106,7 @@
}
});
+
//==============================================================================
return {
AnnotatedHistoryPanel : AnnotatedHistoryPanel
This diff is so big that we needed to truncate the remainder.
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: Fix to 419ca68: re-add ability to re-size right and left panels using mouse events
by commits-noreply@bitbucket.org 08 Sep '14
by commits-noreply@bitbucket.org 08 Sep '14
08 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9d15e8995164/
Changeset: 9d15e8995164
User: carlfeberhard
Date: 2014-09-08 21:10:49
Summary: Fix to 419ca68: re-add ability to re-size right and left panels using mouse events
Affected #: 2 files
diff -r 7c19138e41ada844f5515aba40a6ec7928b11fcb -r 9d15e899516411c9b491f66f001a55136b1173b9 static/scripts/galaxy.panels.js
--- a/static/scripts/galaxy.panels.js
+++ b/static/scripts/galaxy.panels.js
@@ -92,28 +92,32 @@
}
},
init: function() {
- var self = this;
+ var self = this,
+ prevX;
// Pull the collapse element out to body level so it is visible when panel is hidden
- this.$toggle.remove().appendTo( "body" );
- // Resizing using drag element
- this.$drag.on( "dragstart", function( e, d ) {
- $( '#DD-helper' ).show();
- d.width = self.$panel.width();
- }).on( "dragend", function() {
- $( '#DD-helper' ).hide();
- }).on( "drag", function( e, d ) {
- var x;
- if ( self.left ) {
- x = d.width + d.deltaX;
- } else {
- x = d.width - d.deltaX;
- }
- // Limit range
- x = Math.min( MAX_PANEL_WIDTH, Math.max( MIN_PANEL_WIDTH, x ) );
- self.resize( x );
- });
+ self.$toggle.remove().appendTo( "body" );
// Hide/show using toggle element
self.$toggle.on( "click", function() { self.do_toggle(); } );
+
+ // Resizing using drag element
+ function move( e ){
+ var delta = e.pageX - prevX;
+ prevX = e.pageX;
+
+ var oldWidth = self.$panel.width(),
+ newWidth = ( self.left )?( oldWidth + delta ):( oldWidth - delta );
+ // Limit range
+ newWidth = Math.min( MAX_PANEL_WIDTH, Math.max( MIN_PANEL_WIDTH, newWidth ) );
+ self.resize( newWidth );
+ }
+ this.$drag.on( "mousedown", function( e ) {
+ prevX = e.pageX;
+ $( '#DD-helper' ).show()
+ .on( 'mousemove', move )
+ .one( 'mouseup', function( e ){
+ $( this ).hide().off( 'mousemove', move );
+ });
+ });
}
});
diff -r 7c19138e41ada844f5515aba40a6ec7928b11fcb -r 9d15e899516411c9b491f66f001a55136b1173b9 static/scripts/packed/galaxy.panels.js
--- a/static/scripts/packed/galaxy.panels.js
+++ b/static/scripts/packed/galaxy.panels.js
@@ -1,1 +1,1 @@
-!function(c,d){var e=function(){if(d("#DD-helper").length==0){d("<div id='DD-helper'/>").appendTo("body").hide()}};var b=160,h=800;var k=function(n){this.$panel=n.panel;this.$center=n.center;this.$drag=n.drag;this.$toggle=n.toggle;this.left=!n.right;this.hidden=false;this.hidden_by_tool=false;this.saved_size=null;this.init()};d.extend(k.prototype,{resize:function(n){this.$panel.css("width",n);if(this.left){this.$center.css("left",n)}else{this.$center.css("right",n)}if(document.recalc){document.recalc()}},do_toggle:function(){var n=this;if(this.hidden){this.$toggle.removeClass("hidden");if(this.left){this.$panel.css("left",-this.saved_size).show().animate({left:0},"fast",function(){n.resize(n.saved_size)})}else{this.$panel.css("right",-this.saved_size).show().animate({right:0},"fast",function(){n.resize(n.saved_size)})}n.hidden=false}else{n.saved_size=this.$panel.width();if(document.recalc){document.recalc()}if(this.left){this.$panel.animate({left:-this.saved_size},"fast")}else{this.$panel.animate({right:-this.saved_size},"fast")}if(this.left){this.$center.css("left",0)}else{this.$center.css("right",0)}n.hidden=true;n.$toggle.addClass("hidden")}this.hidden_by_tool=false},handle_minwidth_hint:function(n){var o=this.$center.width()-(this.hidden?this.saved_size:0);if(o<n){if(!this.hidden){this.do_toggle();this.hidden_by_tool=true}}else{if(this.hidden_by_tool){this.do_toggle();this.hidden_by_tool=false}}},force_panel:function(n){if((this.hidden&&n=="show")||(!this.hidden&&n=="hide")){this.do_toggle()}},init:function(){var n=this;this.$toggle.remove().appendTo("body");this.$drag.on("dragstart",function(o,p){d("#DD-helper").show();p.width=n.$panel.width()}).on("dragend",function(){d("#DD-helper").hide()}).on("drag",function(p,q){var o;if(n.left){o=q.width+q.deltaX}else{o=q.width-q.deltaX}o=Math.min(h,Math.max(b,o));n.resize(o)});n.$toggle.on("click",function(){n.do_toggle()})}});var f=function(n){this.$overlay=n.overlay;this.$dialog=n.dialog;this.$header=this.$dialog.find(".modal-header");this.$body=this.$dialog.find(".modal-body");this.$footer=this.$dialog.find(".modal-footer");this.$backdrop=n.backdrop;this.$header.find(".close").on("click",d.proxy(this.hide,this))};d.extend(f.prototype,{setContent:function(p){this.$header.hide();if(p.title){this.$header.find(".title").html(p.title);this.$header.show()}if(p.closeButton){this.$header.find(".close").show();this.$header.show()}else{this.$header.find(".close").hide()}this.$footer.hide();var o=this.$footer.find(".buttons").html("");if(p.buttons){d.each(p.buttons,function(r,s){o.append(d("<button></button> ").text(r).click(s)).append(" ")});this.$footer.show()}var q=this.$footer.find(".extra_buttons").html("");if(p.extra_buttons){d.each(p.extra_buttons,function(r,s){q.append(d("<button></button>").text(r).click(s)).append(" ")});this.$footer.show()}var n=p.body;if(n=="progress"){n=d("<div class='progress progress-striped active'><div class='progress-bar' style='width: 100%'></div></div>")}this.$body.html(n)},show:function(n,o){if(!this.$dialog.is(":visible")){if(n.backdrop){this.$backdrop.addClass("in")}else{this.$backdrop.removeClass("in")}this.$overlay.show();this.$dialog.show();this.$overlay.addClass("in");this.$body.css("min-width",this.$body.width());this.$body.css("max-height",d(window).height()-this.$footer.outerHeight()-this.$header.outerHeight()-parseInt(this.$dialog.css("padding-top"),10)-parseInt(this.$dialog.css("padding-bottom"),10))}if(o){o()}},hide:function(){var n=this;n.$dialog.fadeOut(function(){n.$overlay.hide();n.$backdrop.removeClass("in");n.$body.children().remove();n.$body.css("min-width",undefined)})}});var m;d(function(){m=new f({overlay:d("#top-modal"),dialog:d("#top-modal-dialog"),backdrop:d("#top-modal-backdrop")})});function a(){m.hide()}function l(r,n,p,o,q){m.setContent({title:r,body:n,buttons:p,extra_buttons:o});m.show({backdrop:true},q)}function g(r,n,p,o,q){m.setContent({title:r,body:n,buttons:p,extra_buttons:o});m.show({backdrop:false},q)}function j(p){var q=p.width||"600";var o=p.height||"400";var n=p.scroll||"auto";d("#overlay-background").bind("click.overlay",function(){a();d("#overlay-background").unbind("click.overlay")});m.setContent({closeButton:true,title:" ",body:d("<div style='margin: -5px;'><iframe style='margin: 0; padding: 0;' src='"+p.url+"' width='"+q+"' height='"+o+"' scrolling='"+n+"' frameborder='0'></iframe></div>")});m.show({backdrop:true})}function i(n,o){if(n){d(".loggedin-only").show();d(".loggedout-only").hide();d("#user-email").text(n);if(o){d(".admin-only").show()}}else{d(".loggedin-only").hide();d(".loggedout-only").show();d(".admin-only").hide()}}d(function(){var n=d("#masthead ul.nav > li.dropdown > .dropdown-menu");d("body").on("click.nav_popups",function(p){n.hide();d("#DD-helper").hide();if(d(p.target).closest("#masthead ul.nav > li.dropdown > .dropdown-menu").length){return}var o=d(p.target).closest("#masthead ul.nav > li.dropdown");if(o.length){d("#DD-helper").show();o.children(".dropdown-menu").show();p.preventDefault()}})});c.ensure_dd_helper=e;c.Panel=k;c.Modal=f;c.hide_modal=a;c.show_modal=l;c.show_message=g;c.show_in_overlay=j;c.user_changed=i}(window,window.jQuery);
\ No newline at end of file
+!function(c,d){var e=function(){if(d("#DD-helper").length==0){d("<div id='DD-helper'/>").appendTo("body").hide()}};var b=160,h=800;var k=function(n){this.$panel=n.panel;this.$center=n.center;this.$drag=n.drag;this.$toggle=n.toggle;this.left=!n.right;this.hidden=false;this.hidden_by_tool=false;this.saved_size=null;this.init()};d.extend(k.prototype,{resize:function(n){this.$panel.css("width",n);if(this.left){this.$center.css("left",n)}else{this.$center.css("right",n)}if(document.recalc){document.recalc()}},do_toggle:function(){var n=this;if(this.hidden){this.$toggle.removeClass("hidden");if(this.left){this.$panel.css("left",-this.saved_size).show().animate({left:0},"fast",function(){n.resize(n.saved_size)})}else{this.$panel.css("right",-this.saved_size).show().animate({right:0},"fast",function(){n.resize(n.saved_size)})}n.hidden=false}else{n.saved_size=this.$panel.width();if(document.recalc){document.recalc()}if(this.left){this.$panel.animate({left:-this.saved_size},"fast")}else{this.$panel.animate({right:-this.saved_size},"fast")}if(this.left){this.$center.css("left",0)}else{this.$center.css("right",0)}n.hidden=true;n.$toggle.addClass("hidden")}this.hidden_by_tool=false},handle_minwidth_hint:function(n){var o=this.$center.width()-(this.hidden?this.saved_size:0);if(o<n){if(!this.hidden){this.do_toggle();this.hidden_by_tool=true}}else{if(this.hidden_by_tool){this.do_toggle();this.hidden_by_tool=false}}},force_panel:function(n){if((this.hidden&&n=="show")||(!this.hidden&&n=="hide")){this.do_toggle()}},init:function(){var o=this,p;o.$toggle.remove().appendTo("body");o.$toggle.on("click",function(){o.do_toggle()});function n(s){var t=s.pageX-p;p=s.pageX;var q=o.$panel.width(),r=(o.left)?(q+t):(q-t);r=Math.min(h,Math.max(b,r));o.resize(r)}this.$drag.on("mousedown",function(q){p=q.pageX;d("#DD-helper").show().on("mousemove",n).one("mouseup",function(r){d(this).hide().off("mousemove",n)})})}});var f=function(n){this.$overlay=n.overlay;this.$dialog=n.dialog;this.$header=this.$dialog.find(".modal-header");this.$body=this.$dialog.find(".modal-body");this.$footer=this.$dialog.find(".modal-footer");this.$backdrop=n.backdrop;this.$header.find(".close").on("click",d.proxy(this.hide,this))};d.extend(f.prototype,{setContent:function(p){this.$header.hide();if(p.title){this.$header.find(".title").html(p.title);this.$header.show()}if(p.closeButton){this.$header.find(".close").show();this.$header.show()}else{this.$header.find(".close").hide()}this.$footer.hide();var o=this.$footer.find(".buttons").html("");if(p.buttons){d.each(p.buttons,function(r,s){o.append(d("<button></button> ").text(r).click(s)).append(" ")});this.$footer.show()}var q=this.$footer.find(".extra_buttons").html("");if(p.extra_buttons){d.each(p.extra_buttons,function(r,s){q.append(d("<button></button>").text(r).click(s)).append(" ")});this.$footer.show()}var n=p.body;if(n=="progress"){n=d("<div class='progress progress-striped active'><div class='progress-bar' style='width: 100%'></div></div>")}this.$body.html(n)},show:function(n,o){if(!this.$dialog.is(":visible")){if(n.backdrop){this.$backdrop.addClass("in")}else{this.$backdrop.removeClass("in")}this.$overlay.show();this.$dialog.show();this.$overlay.addClass("in");this.$body.css("min-width",this.$body.width());this.$body.css("max-height",d(window).height()-this.$footer.outerHeight()-this.$header.outerHeight()-parseInt(this.$dialog.css("padding-top"),10)-parseInt(this.$dialog.css("padding-bottom"),10))}if(o){o()}},hide:function(){var n=this;n.$dialog.fadeOut(function(){n.$overlay.hide();n.$backdrop.removeClass("in");n.$body.children().remove();n.$body.css("min-width",undefined)})}});var m;d(function(){m=new f({overlay:d("#top-modal"),dialog:d("#top-modal-dialog"),backdrop:d("#top-modal-backdrop")})});function a(){m.hide()}function l(r,n,p,o,q){m.setContent({title:r,body:n,buttons:p,extra_buttons:o});m.show({backdrop:true},q)}function g(r,n,p,o,q){m.setContent({title:r,body:n,buttons:p,extra_buttons:o});m.show({backdrop:false},q)}function j(p){var q=p.width||"600";var o=p.height||"400";var n=p.scroll||"auto";d("#overlay-background").bind("click.overlay",function(){a();d("#overlay-background").unbind("click.overlay")});m.setContent({closeButton:true,title:" ",body:d("<div style='margin: -5px;'><iframe style='margin: 0; padding: 0;' src='"+p.url+"' width='"+q+"' height='"+o+"' scrolling='"+n+"' frameborder='0'></iframe></div>")});m.show({backdrop:true})}function i(n,o){if(n){d(".loggedin-only").show();d(".loggedout-only").hide();d("#user-email").text(n);if(o){d(".admin-only").show()}}else{d(".loggedin-only").hide();d(".loggedout-only").show();d(".admin-only").hide()}}d(function(){var n=d("#masthead ul.nav > li.dropdown > .dropdown-menu");d("body").on("click.nav_popups",function(p){n.hide();d("#DD-helper").hide();if(d(p.target).closest("#masthead ul.nav > li.dropdown > .dropdown-menu").length){return}var o=d(p.target).closest("#masthead ul.nav > li.dropdown");if(o.length){d("#DD-helper").show();o.children(".dropdown-menu").show();p.preventDefault()}})});c.ensure_dd_helper=e;c.Panel=k;c.Modal=f;c.hide_modal=a;c.show_modal=l;c.show_message=g;c.show_in_overlay=j;c.user_changed=i}(window,window.jQuery);
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: Fix for d576558: re-add url_for for webapp and decorators
by commits-noreply@bitbucket.org 08 Sep '14
by commits-noreply@bitbucket.org 08 Sep '14
08 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/7c19138e41ad/
Changeset: 7c19138e41ad
User: carlfeberhard
Date: 2014-09-08 20:28:52
Summary: Fix for d576558: re-add url_for for webapp and decorators
Affected #: 2 files
diff -r 02852a8e49e23cd1090f798e99702057fa24bc1d -r 7c19138e41ada844f5515aba40a6ec7928b11fcb lib/galaxy/web/framework/decorators.py
--- a/lib/galaxy/web/framework/decorators.py
+++ b/lib/galaxy/web/framework/decorators.py
@@ -6,6 +6,7 @@
eggs.require( "Paste" )
import paste.httpexceptions
+from galaxy.web.framework import url_for
from galaxy import util
from galaxy.exceptions import error_codes
from galaxy.exceptions import MessageException
diff -r 02852a8e49e23cd1090f798e99702057fa24bc1d -r 7c19138e41ada844f5515aba40a6ec7928b11fcb lib/galaxy/web/framework/webapp.py
--- a/lib/galaxy/web/framework/webapp.py
+++ b/lib/galaxy/web/framework/webapp.py
@@ -33,6 +33,7 @@
from galaxy.util.sanitize_html import sanitize_html
from galaxy.managers import context
+from galaxy.web.framework import url_for
from galaxy.web.framework import base
from galaxy.web.framework import helpers
from galaxy.web.framework import formbuilder
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: jmchilton: Add very basic functional tests for tool param validation.
by commits-noreply@bitbucket.org 08 Sep '14
by commits-noreply@bitbucket.org 08 Sep '14
08 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/02852a8e49e2/
Changeset: 02852a8e49e2
User: jmchilton
Date: 2014-09-08 16:33:54
Summary: Add very basic functional tests for tool param validation.
Including same test with workflow parameter substition - gotta admit I thought that workflow test was going to fail - so this week is looking pretty good :).
Affected #: 5 files
diff -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 -r 02852a8e49e23cd1090f798e99702057fa24bc1d test/api/test_workflow_validation_1.ga
--- /dev/null
+++ b/test/api/test_workflow_validation_1.ga
@@ -0,0 +1,33 @@
+{
+ "a_galaxy_workflow": "true",
+ "annotation": "",
+ "format-version": "0.1",
+ "name": "test_validation_1",
+ "steps": {
+ "0": {
+ "annotation": "",
+ "id": 0,
+ "input_connections": {},
+ "inputs": [],
+ "name": "Validation (default)",
+ "outputs": [
+ {
+ "name": "out_file1",
+ "type": "data"
+ }
+ ],
+ "position": {
+ "left": 10,
+ "top": 10
+ },
+ "post_job_actions": {},
+ "tool_errors": null,
+ "tool_id": "validation_default",
+ "tool_state": "{\"__page__\": 0, \"__rerun_remap_job_id__\": null, \"input1\": \"\\\"cow\\\"\", \"chromInfo\": \"\\\"/home/john/workspace/galaxy-central/tool-data/shared/ucsc/chrom/?.len\\\"\"}",
+ "tool_version": null,
+ "type": "tool",
+ "user_outputs": []
+ }
+ },
+ "uuid": "d058c6a0-6d70-44f4-a177-01859fc6aa37"
+}
\ No newline at end of file
diff -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 -r 02852a8e49e23cd1090f798e99702057fa24bc1d test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -478,6 +478,21 @@
self.__assert_lines_hid_line_count_is( history_id, 2, 5 )
self.__assert_lines_hid_line_count_is( history_id, 3, 5 )
+ @skip_without_tool( "validation_default" )
+ def test_parameter_substitution_validation( self ):
+ workflow = self.workflow_populator.load_workflow_from_resource( "test_workflow_validation_1" )
+ uploaded_workflow_id = self.workflow_populator.create_workflow( workflow )
+ history_id = self.dataset_populator.new_history()
+ workflow_request = dict(
+ history="hist_id=%s" % history_id,
+ workflow_id=uploaded_workflow_id,
+ parameters=dumps( dict( validation_default=dict( input1="\" ; echo \"moo" ) ) )
+ )
+ run_workflow_response = self._post( "workflows", data=workflow_request )
+ self._assert_status_code_is( run_workflow_response, 200 )
+ self.dataset_populator.wait_for_history( history_id, assert_ok=True )
+ self.assertEquals("__dq__ X echo __dq__moo\n", self.dataset_populator.get_history_dataset_content( history_id ) )
+
@skip_without_tool( "random_lines1" )
def test_run_replace_params_by_steps( self ):
workflow_request, history_id = self._setup_random_x2_workflow( "test_for_replace_step_params" )
diff -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 -r 02852a8e49e23cd1090f798e99702057fa24bc1d test/functional/tools/for_workflows/head.xml
--- a/test/functional/tools/for_workflows/head.xml
+++ b/test/functional/tools/for_workflows/head.xml
@@ -1,6 +1,6 @@
<tool id="head" name="Select first"><description>lines from a dataset</description>
- <command>head $input $lineNum > $out_file1</command>
+ <command>head -n $lineNum $input > $out_file1</command><inputs><param name="lineNum" size="5" type="integer" value="10" label="Select first" help="lines"/><param format="txt" name="input" type="data" label="from"/>
diff -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 -r 02852a8e49e23cd1090f798e99702057fa24bc1d test/functional/tools/samples_tool_conf.xml
--- a/test/functional/tools/samples_tool_conf.xml
+++ b/test/functional/tools/samples_tool_conf.xml
@@ -21,6 +21,7 @@
<tool file="implicit_default_conds.xml" /><tool file="multi_data_param.xml" /><tool file="special_params.xml" />
+ <tool file="validation_default.xml" /><tool file="collection_paired_test.xml" /><tool file="collection_nested_test.xml" /><tool file="collection_mixed_param.xml" />
diff -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 -r 02852a8e49e23cd1090f798e99702057fa24bc1d test/functional/tools/validation_default.xml
--- /dev/null
+++ b/test/functional/tools/validation_default.xml
@@ -0,0 +1,23 @@
+<tool id="validation_default" name="Validation (default)">
+ <command>
+ echo "$input1" > out1;
+ </command>
+ <inputs>
+ <param name="input1" type="text" label="Concatenate Dataset" />
+ </inputs>
+ <outputs>
+ <data name="out_file1" from_work_dir="out1" />
+ </outputs>
+ <tests>
+ <test>
+ <param name="input1" value="" ; echo "moo" />
+ <output name="out_file1">
+ <assert_contents>
+ <has_line line="__dq__ X echo __dq__moo" />
+ </assert_contents>
+ </output>
+ </test>
+ </tests>
+ <help>
+ </help>
+</tool>
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: guerler: ToolForm: Add jobs module, fix minor issues and revise tool form style
by commits-noreply@bitbucket.org 06 Sep '14
by commits-noreply@bitbucket.org 06 Sep '14
06 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/f295824b2f9e/
Changeset: f295824b2f9e
User: guerler
Date: 2014-09-06 22:08:35
Summary: ToolForm: Add jobs module, fix minor issues and revise tool form style
Affected #: 16 files
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/mvc/tools/tools-form.js
--- a/static/scripts/mvc/tools/tools-form.js
+++ b/static/scripts/mvc/tools/tools-form.js
@@ -102,7 +102,7 @@
title : 'Execute',
floating : 'clear',
onclick : function() {
- console.log(self.tree.create(self));
+ self._submit();
}
})
},
@@ -173,6 +173,11 @@
// log
console.debug('tools-form::refresh() - Recreated tree structure. Refresh.');
+ },
+
+ // submit
+ _submit: function() {
+ console.log(this.tree.jobDictionary());
}
});
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/mvc/tools/tools-jobs.js
--- /dev/null
+++ b/static/scripts/mvc/tools/tools-jobs.js
@@ -0,0 +1,49 @@
+// dependencies
+define(['utils/utils'], function(Utils) {
+
+/**
+ * This class handles job submissions.
+ */
+return Backbone.Model.extend({
+ // initialize
+ initialize: function(app, options) {
+ // link app
+ this.app = app;
+
+ // link options
+ this.options = Utils.merge(options, this.optionsDefault);
+ },
+
+ // create job
+ submit: function(data, success, error) {
+ // link this
+ var self = this;
+
+ // post job
+ Utils.request('POST', config.root + 'api/tools', data,
+ // success handler
+ function(response) {
+ if (!response.outputs || response.outputs.length == 0) {
+ // call error
+ error && error();
+ } else {
+ // update galaxy history
+ console.log(response);
+ }
+ },
+ // error handler
+ function(response) {
+ var message = '';
+ if (response && response.message && response.message.data && response.message.data.input) {
+ message = response.message.data.input + '.';
+ }
+
+ // call error
+ error && error();
+
+ }
+ );
+ }
+});
+
+});
\ No newline at end of file
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/mvc/tools/tools-section.js
--- a/static/scripts/mvc/tools/tools-section.js
+++ b/static/scripts/mvc/tools/tools-section.js
@@ -89,9 +89,15 @@
cls : 'ui-table-plain'
});
- // append sub section
- this.table.add('');
- this.table.add(sub_section.$el);
+ // create table row
+ this.table.add(this._create_field({
+ label : '',
+ help : input_def.help,
+ $el : sub_section.$el,
+ color : true
+ }));
+
+ // append to table
this.table.append(sub_section_id);
}
},
@@ -169,9 +175,15 @@
// retitle tabs
tabs.retitle(input_def.title);
- // append sub section
- this.table.add('');
- this.table.add(tabs.$el);
+ // create table row
+ this.table.add(this._create_field({
+ label : input_def.title,
+ help : input_def.help,
+ $el : tabs.$el,
+ color : true
+ }));
+
+ // append row to table
this.table.append(input_def.id);
},
@@ -253,16 +265,12 @@
// add to field list
this.app.field_list[id] = field;
- // combine field and info
- var $input = $('<div/>');
- $input.append(field.$el);
- if (input_def.help) {
- $input.append('<div class="ui-table-form-info">' + input_def.help + '</div>');
- }
-
// create table row
- this.table.add('<span class="ui-table-form-title">' + input_def.label + '</span>', '20%');
- this.table.add($input);
+ this.table.add(this._create_field({
+ label : input_def.label,
+ help : input_def.help,
+ $el : field.$el
+ }));
// append to table
this.table.append(id);
@@ -480,6 +488,24 @@
data : [ { label : 'Yes', value : true },
{ label : 'No', value : false }]
});
+ },
+
+ // create a field element with title and help information
+ _create_field: function(options) {
+ var $input;
+ if (options.color) {
+ $input = $('<div class="ui-table-form-section"/>');
+ } else {
+ $input = $('<div/>');
+ }
+ if (options.label) {
+ $input.append('<div class="ui-table-form-title-strong">' + options.label + '</div>');
+ }
+ $input.append(options.$el);
+ if (options.help) {
+ $input.append('<div class="ui-table-form-info">' + options.help + '</div>');
+ }
+ return $input;
}
});
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/mvc/tools/tools-select-dataset.js
--- a/static/scripts/mvc/tools/tools-select-dataset.js
+++ b/static/scripts/mvc/tools/tools-select-dataset.js
@@ -56,7 +56,11 @@
});
// tabs
- this.tabs = new Tabs.View();
+ this.tabs = new Tabs.View({
+ onchange: function() {
+ self.trigger('change');
+ }
+ });
// add tab
this.tabs.add({
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/mvc/tools/tools-tree.js
--- a/static/scripts/mvc/tools/tools-tree.js
+++ b/static/scripts/mvc/tools/tools-tree.js
@@ -26,6 +26,46 @@
this._iterate(this.app.section.$el, this.dict, this.xml);
},
+
+ // convert to job dictionary
+ jobDictionary: function() {
+ // link this
+ var self = this;
+
+ // dictionary formatted for job submission
+ var job_def = {};
+
+ // converter between raw dictionary and job dictionary
+ function convert(identifier, head) {
+ for (var i in head) {
+ if (head[i].input) {
+ var input = head[i].input;
+ var job_input_id = identifier + input.name;
+
+ if (input.type == 'repeat') {
+
+
+ } else {
+ //var value = self.app.field_list[input.id].value();
+ switch(input.type) {
+ case 'repeat':
+ job_def[job_input_id] = value;
+ break;
+ default:
+ job_def[job_input_id] = value;
+ }
+ }
+ }
+ }
+ }
+
+ // start conversion
+ convert('', this.dict);
+
+ // return result
+ return job_def;
+ },
+
// iterate
_iterate: function(parent, dict, xml) {
// get child nodes
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/mvc/ui/ui-slider.js
--- a/static/scripts/mvc/ui/ui-slider.js
+++ b/static/scripts/mvc/ui/ui-slider.js
@@ -73,9 +73,9 @@
// element
_template: function(options) {
- return '<div id="' + options.id + '" style="">' +
- '<input id="text" type="text" class="ui-input" style="width: 50px; float: left"/>' +
- '<div id="slider" style="width: calc(100% - 60px); float: left; top: 8px; left: 10px;"/>' +
+ return '<div id="' + options.id + '" class="ui-form-slider">' +
+ '<input id="text" type="text" class="ui-form-slider-text"/>' +
+ '<div id="slider" class="ui-form-slider-element"/>' +
'</div>';
}
});
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/mvc/ui/ui-tabs.js
--- a/static/scripts/mvc/ui/ui-tabs.js
+++ b/static/scripts/mvc/ui/ui-tabs.js
@@ -9,7 +9,8 @@
operations : null,
onnew : null,
min : null,
- max : null
+ max : null,
+ onchange : null
},
// initialize
@@ -187,6 +188,11 @@
this.$el.find('#tab-' + id).addClass('active');
this.$el.find('#' + id).addClass('active');
}
+
+ // change
+ if (this.options.onchange) {
+ this.options.onchange(id);
+ }
},
// hide
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/packed/mvc/tools/tools-form.js
--- a/static/scripts/packed/mvc/tools/tools-form.js
+++ b/static/scripts/packed/mvc/tools/tools-form.js
@@ -1,1 +1,1 @@
-define(["mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/citation/citation-model","mvc/citation/citation-view","mvc/tools","mvc/tools/tools-template","mvc/tools/tools-datasets","mvc/tools/tools-section","mvc/tools/tools-tree"],function(g,k,i,a,f,d,h,j,c){var e=Backbone.Model.extend({initialize:function(l){this.url=galaxy_config.root+"api/tools/"+l.id+"?io_details=true"}});var b=Backbone.View.extend({main_el:"body",initialize:function(m){var l=this;this.options=m;this.model=new e({id:m.id});this.tree=new c(this);this.field_list={};this.input_list={};this.datasets=new h({history_id:this.options.history_id,success:function(){l._initializeToolForm()}})},_initializeToolForm:function(){var m=this;var n=new k.ButtonIcon({icon:"fa-question-circle",title:"Question?",tooltip:"Ask a question about this tool (Biostar)",onclick:function(){window.open(m.options.biostar_url+"/p/new/post/")}});var o=new k.ButtonIcon({icon:"fa-search",title:"Search",tooltip:"Search help for this tool (Biostar)",onclick:function(){window.open(m.options.biostar_url+"/t/"+m.options.id+"/")}});var l=new k.ButtonIcon({icon:"fa-share",title:"Share",tooltip:"Share this tool",onclick:function(){prompt("Copy to clipboard: Ctrl+C, Enter",galaxy_config.root+"root?tool_id="+m.options.id)}});this.model.fetch({error:function(p){console.debug("tools-form::_initializeToolForm() : Attempt to fetch tool model failed.")},success:function(){m.inputs=m.model.get("inputs");m.portlet=new g.View({icon:"fa-wrench",title:"<b>"+m.model.get("name")+"</b> "+m.model.get("description"),buttons:{execute:new k.ButtonIcon({icon:"fa-check",tooltip:"Execute the tool",title:"Execute",floating:"clear",onclick:function(){console.log(m.tree.create(m))}})},operations:{button_question:n,button_search:o,button_share:l}});if(!m.options.biostar_url){n.$el.hide();o.$el.hide()}m.message=new k.Message();m.portlet.append(m.message.$el);$(m.main_el).append(m.portlet.$el);if(m.options.help!=""){$(m.main_el).append(d.help(m.options.help))}if(m.options.citations){$(m.main_el).append(d.citations());var p=new i.ToolCitationCollection();p.tool_id=m.options.id;var q=new a.CitationListView({collection:p});q.render();p.fetch()}m.setElement(m.portlet.content());m.section=new j.View(m,{inputs:m.model.get("inputs")});m.portlet.append(m.section.$el);m.refresh()}})},refresh:function(){this.tree.refresh();for(var l in this.field_list){this.field_list[l].trigger("change")}console.debug("tools-form::refresh() - Recreated tree structure. Refresh.")}});return{View:b}});
\ No newline at end of file
+define(["mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/citation/citation-model","mvc/citation/citation-view","mvc/tools","mvc/tools/tools-template","mvc/tools/tools-datasets","mvc/tools/tools-section","mvc/tools/tools-tree"],function(g,k,i,a,f,d,h,j,c){var e=Backbone.Model.extend({initialize:function(l){this.url=galaxy_config.root+"api/tools/"+l.id+"?io_details=true"}});var b=Backbone.View.extend({main_el:"body",initialize:function(m){var l=this;this.options=m;this.model=new e({id:m.id});this.tree=new c(this);this.field_list={};this.input_list={};this.datasets=new h({history_id:this.options.history_id,success:function(){l._initializeToolForm()}})},_initializeToolForm:function(){var m=this;var n=new k.ButtonIcon({icon:"fa-question-circle",title:"Question?",tooltip:"Ask a question about this tool (Biostar)",onclick:function(){window.open(m.options.biostar_url+"/p/new/post/")}});var o=new k.ButtonIcon({icon:"fa-search",title:"Search",tooltip:"Search help for this tool (Biostar)",onclick:function(){window.open(m.options.biostar_url+"/t/"+m.options.id+"/")}});var l=new k.ButtonIcon({icon:"fa-share",title:"Share",tooltip:"Share this tool",onclick:function(){prompt("Copy to clipboard: Ctrl+C, Enter",galaxy_config.root+"root?tool_id="+m.options.id)}});this.model.fetch({error:function(p){console.debug("tools-form::_initializeToolForm() : Attempt to fetch tool model failed.")},success:function(){m.inputs=m.model.get("inputs");m.portlet=new g.View({icon:"fa-wrench",title:"<b>"+m.model.get("name")+"</b> "+m.model.get("description"),buttons:{execute:new k.ButtonIcon({icon:"fa-check",tooltip:"Execute the tool",title:"Execute",floating:"clear",onclick:function(){m._submit()}})},operations:{button_question:n,button_search:o,button_share:l}});if(!m.options.biostar_url){n.$el.hide();o.$el.hide()}m.message=new k.Message();m.portlet.append(m.message.$el);$(m.main_el).append(m.portlet.$el);if(m.options.help!=""){$(m.main_el).append(d.help(m.options.help))}if(m.options.citations){$(m.main_el).append(d.citations());var p=new i.ToolCitationCollection();p.tool_id=m.options.id;var q=new a.CitationListView({collection:p});q.render();p.fetch()}m.setElement(m.portlet.content());m.section=new j.View(m,{inputs:m.model.get("inputs")});m.portlet.append(m.section.$el);m.refresh()}})},refresh:function(){this.tree.refresh();for(var l in this.field_list){this.field_list[l].trigger("change")}console.debug("tools-form::refresh() - Recreated tree structure. Refresh.")},_submit:function(){console.log(this.tree.jobDictionary())}});return{View:b}});
\ No newline at end of file
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/packed/mvc/tools/tools-jobs.js
--- /dev/null
+++ b/static/scripts/packed/mvc/tools/tools-jobs.js
@@ -0,0 +1,1 @@
+define(["utils/utils"],function(a){return Backbone.Model.extend({initialize:function(c,b){this.app=c;this.options=a.merge(b,this.optionsDefault)},submit:function(d,e,c){var b=this;a.request("POST",config.root+"api/tools",d,function(f){if(!f.outputs||f.outputs.length==0){c&&c()}else{console.log(f)}},function(f){var g="";if(f&&f.message&&f.message.data&&f.message.data.input){g=f.message.data.input+"."}c&&c()})}})});
\ No newline at end of file
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/packed/mvc/tools/tools-section.js
--- a/static/scripts/packed/mvc/tools/tools-section.js
+++ b/static/scripts/packed/mvc/tools/tools-section.js
@@ -1,1 +1,1 @@
-define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-tabs","mvc/tools/tools-select-dataset"],function(d,b,f,a,c){var e=Backbone.View.extend({initialize:function(h,g){this.app=h;this.inputs=g.inputs;g.cls_tr="section-row";this.table=new b.View(g);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var g in this.inputs){this._add(this.inputs[g])}},_add:function(i){var h=this;var g=jQuery.extend(true,{},i);g.id=d.uuid();this.app.input_list[g.id]=g;var j=g.type;switch(j){case"conditional":this._addConditional(g);break;case"repeat":this._addRepeat(g);break;default:this._addRow(j,g)}},_addConditional:function(g){g.label=g.test_param.label;g.value=g.test_param.value;this._addRow("conditional",g);for(var j in g.cases){var h=g.id+"-section-"+j;var k=new e(this.app,{inputs:g.cases[j].inputs,cls:"ui-table-plain"});this.table.add("");this.table.add(k.$el);this.table.append(h)}},_addRepeat:function(g){var h=this;var l=new a.View({title_new:"Add "+g.title,max:g.max,onnew:function(){var i=g.id+"-section-"+d.uuid();var n=new e(h.app,{inputs:g.inputs,cls:"ui-table-plain"});l.add({id:i,title:g.title,$el:n.$el,ondel:function(){l.del(i);l.retitle(g.title);h.app.refresh()}});l.retitle(g.title);l.show(i);h.app.refresh()}});for(var k=0;k<g.min;k++){var j=g.id+"-section-"+d.uuid();var m=new e(h.app,{inputs:g.inputs,cls:"ui-table-plain"});l.add({id:j,title:g.title,$el:m.$el})}l.retitle(g.title);this.table.add("");this.table.add(l.$el);this.table.append(g.id)},_addRow:function(i,g){var k=g.id;var h=null;switch(i){case"text":h=this._field_text(g);break;case"select":h=this._field_select(g);break;case"data":h=this._field_data(g);break;case"data_column":h=this._field_data_colum(g);break;case"conditional":h=this._field_conditional(g);break;case"hidden":h=this._field_hidden(g);break;case"integer":h=this._field_slider(g);break;case"float":h=this._field_slider(g);break;case"boolean":h=this._field_boolean(g);break}if(!h){if(g.options){h=this._field_select(g)}else{h=this._field_text(g)}console.debug("tools-form::_addRow() : Auto matched field type ("+i+").")}if(g.value!==undefined){h.value(g.value)}this.app.field_list[k]=h;var j=$("<div/>");j.append(h.$el);if(g.help){j.append('<div class="ui-table-form-info">'+g.help+"</div>")}this.table.add('<span class="ui-table-form-title">'+g.label+"</span>","20%");this.table.add(j);this.table.append(k)},_field_conditional:function(g){var h=this;var j=[];for(var k in g.test_param.options){var l=g.test_param.options[k];j.push({label:l[0],value:l[1]})}return new f.Select.View({id:"field-"+g.id,data:j,onchange:function(t){for(var r in g.cases){var n=g.cases[r];var q=g.id+"-section-"+r;var m=h.table.get(q);var p=false;for(var o in n.inputs){var s=n.inputs[o].type;if(s&&s!=="hidden"){p=true;break}}if(n.value==t&&p){m.fadeIn("fast")}else{m.hide()}}}})},_field_data:function(g){var h=this;var i=g.id;return new c.View(this.app,{id:"field-"+i,extensions:g.extensions,multiple:g.multiple,onchange:function(r){if(r instanceof Array){r=r[0]}var p=h.app.tree.findReferences(i,"data_column");var k=h.app.datasets.filter(r);if(k&&p.length>0){console.debug("tool-form::field_data() - Selected dataset "+r+".");var t=k.get("metadata_column_types");if(!t){console.debug("tool-form::field_data() - FAILED: Could not find metadata for dataset "+r+".")}for(var m in p){var n=h.app.input_list[p[m]];var o=h.app.field_list[p[m]];if(!n||!o){console.debug("tool-form::field_data() - FAILED: Column not found.")}var l=n.numerical;var j=[];for(var s in t){var q=t[s];if(q=="int"||q=="float"||!l){j.push({label:"Column: "+(parseInt(s)+1)+" ["+t[s]+"]",value:s})}}if(o){o.update(j);if(!o.exists(o.value())){o.value(o.first())}}}}else{console.debug("tool-form::field_data() - FAILED: Could not find dataset "+r+".")}}})},_field_select:function(g){var h=[];for(var j in g.options){var k=g.options[j];h.push({label:k[0],value:k[1]})}var l=f.Select;switch(g.display){case"checkboxes":l=f.Checkbox;break;case"radio":l=f.RadioButton;break}return new l.View({id:"field-"+g.id,data:h,multiple:g.multiple})},_field_data_colum:function(g){return new f.Select.View({id:"field-"+g.id,multiple:g.multiple})},_field_text:function(g){return new f.Input({id:"field-"+g.id,area:g.area})},_field_slider:function(g){var h=1;if(g.type=="float"){h=(g.max-g.min)/10000}return new f.Slider.View({id:"field-"+g.id,min:g.min||0,max:g.max||1000,step:h})},_field_hidden:function(g){return new f.Hidden({id:"field-"+g.id})},_field_boolean:function(g){return new f.RadioButton.View({id:"field-"+g.id,data:[{label:"Yes",value:true},{label:"No",value:false}]})}});return{View:e}});
\ No newline at end of file
+define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-tabs","mvc/tools/tools-select-dataset"],function(d,b,f,a,c){var e=Backbone.View.extend({initialize:function(h,g){this.app=h;this.inputs=g.inputs;g.cls_tr="section-row";this.table=new b.View(g);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var g in this.inputs){this._add(this.inputs[g])}},_add:function(i){var h=this;var g=jQuery.extend(true,{},i);g.id=d.uuid();this.app.input_list[g.id]=g;var j=g.type;switch(j){case"conditional":this._addConditional(g);break;case"repeat":this._addRepeat(g);break;default:this._addRow(j,g)}},_addConditional:function(g){g.label=g.test_param.label;g.value=g.test_param.value;this._addRow("conditional",g);for(var j in g.cases){var h=g.id+"-section-"+j;var k=new e(this.app,{inputs:g.cases[j].inputs,cls:"ui-table-plain"});this.table.add(this._create_field({label:"",help:g.help,$el:k.$el,color:true}));this.table.append(h)}},_addRepeat:function(g){var h=this;var l=new a.View({title_new:"Add "+g.title,max:g.max,onnew:function(){var i=g.id+"-section-"+d.uuid();var n=new e(h.app,{inputs:g.inputs,cls:"ui-table-plain"});l.add({id:i,title:g.title,$el:n.$el,ondel:function(){l.del(i);l.retitle(g.title);h.app.refresh()}});l.retitle(g.title);l.show(i);h.app.refresh()}});for(var k=0;k<g.min;k++){var j=g.id+"-section-"+d.uuid();var m=new e(h.app,{inputs:g.inputs,cls:"ui-table-plain"});l.add({id:j,title:g.title,$el:m.$el})}l.retitle(g.title);this.table.add(this._create_field({label:g.title,help:g.help,$el:l.$el,color:true}));this.table.append(g.id)},_addRow:function(i,g){var j=g.id;var h=null;switch(i){case"text":h=this._field_text(g);break;case"select":h=this._field_select(g);break;case"data":h=this._field_data(g);break;case"data_column":h=this._field_data_colum(g);break;case"conditional":h=this._field_conditional(g);break;case"hidden":h=this._field_hidden(g);break;case"integer":h=this._field_slider(g);break;case"float":h=this._field_slider(g);break;case"boolean":h=this._field_boolean(g);break}if(!h){if(g.options){h=this._field_select(g)}else{h=this._field_text(g)}console.debug("tools-form::_addRow() : Auto matched field type ("+i+").")}if(g.value!==undefined){h.value(g.value)}this.app.field_list[j]=h;this.table.add(this._create_field({label:g.label,help:g.help,$el:h.$el}));this.table.append(j)},_field_conditional:function(g){var h=this;var j=[];for(var k in g.test_param.options){var l=g.test_param.options[k];j.push({label:l[0],value:l[1]})}return new f.Select.View({id:"field-"+g.id,data:j,onchange:function(t){for(var r in g.cases){var n=g.cases[r];var q=g.id+"-section-"+r;var m=h.table.get(q);var p=false;for(var o in n.inputs){var s=n.inputs[o].type;if(s&&s!=="hidden"){p=true;break}}if(n.value==t&&p){m.fadeIn("fast")}else{m.hide()}}}})},_field_data:function(g){var h=this;var i=g.id;return new c.View(this.app,{id:"field-"+i,extensions:g.extensions,multiple:g.multiple,onchange:function(r){if(r instanceof Array){r=r[0]}var p=h.app.tree.findReferences(i,"data_column");var k=h.app.datasets.filter(r);if(k&&p.length>0){console.debug("tool-form::field_data() - Selected dataset "+r+".");var t=k.get("metadata_column_types");if(!t){console.debug("tool-form::field_data() - FAILED: Could not find metadata for dataset "+r+".")}for(var m in p){var n=h.app.input_list[p[m]];var o=h.app.field_list[p[m]];if(!n||!o){console.debug("tool-form::field_data() - FAILED: Column not found.")}var l=n.numerical;var j=[];for(var s in t){var q=t[s];if(q=="int"||q=="float"||!l){j.push({label:"Column: "+(parseInt(s)+1)+" ["+t[s]+"]",value:s})}}if(o){o.update(j);if(!o.exists(o.value())){o.value(o.first())}}}}else{console.debug("tool-form::field_data() - FAILED: Could not find dataset "+r+".")}}})},_field_select:function(g){var h=[];for(var j in g.options){var k=g.options[j];h.push({label:k[0],value:k[1]})}var l=f.Select;switch(g.display){case"checkboxes":l=f.Checkbox;break;case"radio":l=f.RadioButton;break}return new l.View({id:"field-"+g.id,data:h,multiple:g.multiple})},_field_data_colum:function(g){return new f.Select.View({id:"field-"+g.id,multiple:g.multiple})},_field_text:function(g){return new f.Input({id:"field-"+g.id,area:g.area})},_field_slider:function(g){var h=1;if(g.type=="float"){h=(g.max-g.min)/10000}return new f.Slider.View({id:"field-"+g.id,min:g.min||0,max:g.max||1000,step:h})},_field_hidden:function(g){return new f.Hidden({id:"field-"+g.id})},_field_boolean:function(g){return new f.RadioButton.View({id:"field-"+g.id,data:[{label:"Yes",value:true},{label:"No",value:false}]})},_create_field:function(g){var h;if(g.color){h=$('<div class="ui-table-form-section"/>')}else{h=$("<div/>")}if(g.label){h.append('<div class="ui-table-form-title-strong">'+g.label+"</div>")}h.append(g.$el);if(g.help){h.append('<div class="ui-table-form-info">'+g.help+"</div>")}return h}});return{View:e}});
\ No newline at end of file
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/packed/mvc/tools/tools-select-dataset.js
--- a/static/scripts/packed/mvc/tools/tools-select-dataset.js
+++ b/static/scripts/packed/mvc/tools/tools-select-dataset.js
@@ -1,1 +1,1 @@
-define(["utils/utils","mvc/ui/ui-misc","mvc/ui/ui-tabs"],function(b,d,a){var c=Backbone.View.extend({initialize:function(k,f){var e=this;var h=k.datasets.filterType();var j=[];for(var g in h){j.push({label:h[g].get("name"),value:h[g].get("id")})}this.select=new d.Select.View({data:j,value:j[0].value,onchange:function(){e.trigger("change")}});this.select_multiple=new d.Select.View({multiple:true,data:j,value:j[0].value,onchange:function(){e.trigger("change")}});this.select_collection=new d.Select.View({data:j,value:j[0].value,onchange:function(){e.trigger("change")}});this.on("change",function(){if(f.onchange){f.onchange(e.value())}});this.tabs=new a.View();this.tabs.add({id:"single",title:"Select a dataset",$el:this.select.$el});this.tabs.add({id:"multiple",title:"Select multiple datasets",$el:this.select_multiple.$el});this.tabs.add({id:"collection",title:"Select a dataset collection",$el:this.select_collection.$el});this.setElement(this.tabs.$el)},value:function(e){var f=this.tabs.current();switch(f){case"multiple":return this.select_multiple.value();case"collection":return this.select_collection.value();default:return this.select.value()}},update:function(e){this.select.update(e)}});return{View:c}});
\ No newline at end of file
+define(["utils/utils","mvc/ui/ui-misc","mvc/ui/ui-tabs"],function(b,d,a){var c=Backbone.View.extend({initialize:function(k,f){var e=this;var h=k.datasets.filterType();var j=[];for(var g in h){j.push({label:h[g].get("name"),value:h[g].get("id")})}this.select=new d.Select.View({data:j,value:j[0].value,onchange:function(){e.trigger("change")}});this.select_multiple=new d.Select.View({multiple:true,data:j,value:j[0].value,onchange:function(){e.trigger("change")}});this.select_collection=new d.Select.View({data:j,value:j[0].value,onchange:function(){e.trigger("change")}});this.on("change",function(){if(f.onchange){f.onchange(e.value())}});this.tabs=new a.View({onchange:function(){e.trigger("change")}});this.tabs.add({id:"single",title:"Select a dataset",$el:this.select.$el});this.tabs.add({id:"multiple",title:"Select multiple datasets",$el:this.select_multiple.$el});this.tabs.add({id:"collection",title:"Select a dataset collection",$el:this.select_collection.$el});this.setElement(this.tabs.$el)},value:function(e){var f=this.tabs.current();switch(f){case"multiple":return this.select_multiple.value();case"collection":return this.select_collection.value();default:return this.select.value()}},update:function(e){this.select.update(e)}});return{View:c}});
\ No newline at end of file
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/packed/mvc/tools/tools-tree.js
--- a/static/scripts/packed/mvc/tools/tools-tree.js
+++ b/static/scripts/packed/mvc/tools/tools-tree.js
@@ -1,1 +1,1 @@
-define([],function(){return Backbone.Model.extend({initialize:function(a){this.app=a},refresh:function(){if(!this.app.section){return{}}this.dict={};this.xml=$("<div/>");this._iterate(this.app.section.$el,this.dict,this.xml)},_iterate:function(d,e,b){var a=this;var c=$(d).children();c.each(function(){var i=this;var h=$(i).attr("id");if($(i).hasClass("section-row")||$(i).hasClass("tab-pane")){e[h]={};var f=a.app.input_list[h];if(f){e[h]={input:f}}var g=$('<div id="'+h+'"/>');b.append(g);a._iterate(i,e[h],g)}else{a._iterate(i,e,b)}})},findReferences:function(c,e){var g=[];var b=this;function d(h,j){var i=$(j).children();var l=[];var k=false;i.each(function(){var o=this;var n=$(o).attr("id");if(n!==c){var m=b.app.input_list[n];if(m){if(m.name==h){k=true;return false}if(m.data_ref==h&&m.type==e){l.push(n)}}}});if(!k){g=g.concat(l);i.each(function(){d(h,this)})}}var f=this.xml.find("#"+c);if(f.length>0){var a=this.app.input_list[c];if(a){d(a.name,f.parent())}}return g}})});
\ No newline at end of file
+define([],function(){return Backbone.Model.extend({initialize:function(a){this.app=a},refresh:function(){if(!this.app.section){return{}}this.dict={};this.xml=$("<div/>");this._iterate(this.app.section.$el,this.dict,this.xml)},jobDictionary:function(){var a=this;var b={};function c(e,g){for(var f in g){if(g[f].input){var d=g[f].input;var h=e+d.name;if(d.type=="repeat"){}else{switch(d.type){case"repeat":b[h]=value;break;default:b[h]=value}}}}}c("",this.dict);return b},_iterate:function(d,e,b){var a=this;var c=$(d).children();c.each(function(){var i=this;var h=$(i).attr("id");if($(i).hasClass("section-row")||$(i).hasClass("tab-pane")){e[h]={};var f=a.app.input_list[h];if(f){e[h]={input:f}}var g=$('<div id="'+h+'"/>');b.append(g);a._iterate(i,e[h],g)}else{a._iterate(i,e,b)}})},findReferences:function(c,e){var g=[];var b=this;function d(h,j){var i=$(j).children();var l=[];var k=false;i.each(function(){var o=this;var n=$(o).attr("id");if(n!==c){var m=b.app.input_list[n];if(m){if(m.name==h){k=true;return false}if(m.data_ref==h&&m.type==e){l.push(n)}}}});if(!k){g=g.concat(l);i.each(function(){d(h,this)})}}var f=this.xml.find("#"+c);if(f.length>0){var a=this.app.input_list[c];if(a){d(a.name,f.parent())}}return g}})});
\ No newline at end of file
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/packed/mvc/ui/ui-slider.js
--- a/static/scripts/packed/mvc/ui/ui-slider.js
+++ b/static/scripts/packed/mvc/ui/ui-slider.js
@@ -1,1 +1,1 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{value:"",min:1,max:100,step:1},initialize:function(d){var c=this;this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.$slider=this.$el.find("#slider");this.$text=this.$el.find("#text");this.$slider.slider(this.options);this.$text.on("change",function(){c.value($(this).val())});this.$text.on("keydown",function(f){var e=f.which;if(!(e==13||e==8||e==37||e==39||e==189||(e>=48&&e<=57)||(c.options.step!=1&&$(this).val().indexOf(".")==-1)&&e==190)){f.preventDefault()}});this.$slider.on("slide",function(e,f){c.value(f.value)})},value:function(c){if(c!==undefined){c=Math.max(Math.min(c,this.options.max),this.options.min);if(this.options.onchange){this.options.onchange(c)}this.$slider.slider("value",c);this.$text.val(c)}return this.$text.val()},_template:function(c){return'<div id="'+c.id+'" style=""><input id="text" type="text" class="ui-input" style="width: 50px; float: left"/><div id="slider" style="width: calc(100% - 60px); float: left; top: 8px; left: 10px;"/></div>'}});return{View:b}});
\ No newline at end of file
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{value:"",min:1,max:100,step:1},initialize:function(d){var c=this;this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.$slider=this.$el.find("#slider");this.$text=this.$el.find("#text");this.$slider.slider(this.options);this.$text.on("change",function(){c.value($(this).val())});this.$text.on("keydown",function(f){var e=f.which;if(!(e==13||e==8||e==37||e==39||e==189||(e>=48&&e<=57)||(c.options.step!=1&&$(this).val().indexOf(".")==-1)&&e==190)){f.preventDefault()}});this.$slider.on("slide",function(e,f){c.value(f.value)})},value:function(c){if(c!==undefined){c=Math.max(Math.min(c,this.options.max),this.options.min);if(this.options.onchange){this.options.onchange(c)}this.$slider.slider("value",c);this.$text.val(c)}return this.$text.val()},_template:function(c){return'<div id="'+c.id+'" class="ui-form-slider"><input id="text" type="text" class="ui-form-slider-text"/><div id="slider" class="ui-form-slider-element"/></div>'}});return{View:b}});
\ No newline at end of file
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/scripts/packed/mvc/ui/ui-tabs.js
--- a/static/scripts/packed/mvc/ui/ui-tabs.js
+++ b/static/scripts/packed/mvc/ui/ui-tabs.js
@@ -1,1 +1,1 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{title_new:"",operations:null,onnew:null,min:null,max:null},initialize:function(e){this.visible=false;this.$nav=null;this.$content=null;this.first_tab=null;this.options=a.merge(e,this.optionsDefault);var c=$(this._template(this.options));this.$nav=c.find(".tab-navigation");this.$content=c.find(".tab-content");this.setElement(c);this.list={};var d=this;if(this.options.operations){$.each(this.options.operations,function(g,h){h.$el.prop("id",g);d.$nav.find(".operations").append(h.$el)})}if(this.options.onnew){var f=$(this._template_tab_new(this.options));this.$nav.append(f);f.tooltip({title:"Add a new tab",placement:"bottom",container:d.$el});f.on("click",function(g){f.tooltip("hide");d.options.onnew()})}},size:function(){return _.size(this.list)},current:function(){return this.$el.find(".tab-pane.active").attr("id")},add:function(f){var e=this;var h=f.id;var g=$(this._template_tab(f));var d=$(this._template_tab_content(f));this.list[h]=f.ondel?true:false;if(this.options.onnew){this.$nav.find("#new-tab").before(g)}else{this.$nav.append(g)}d.append(f.$el);this.$content.append(d);if(this.size()==1){g.addClass("active");d.addClass("active");this.first_tab=h}if(this.options.max&&this.size()>=this.options.max){this.$el.find("#new-tab").hide()}if(f.ondel){var c=g.find("#delete");c.tooltip({title:"Delete this tab",placement:"bottom",container:e.$el});c.on("click",function(){c.tooltip("destroy");e.$el.find(".tooltip").remove();f.ondel();return false})}g.on("click",function(i){i.preventDefault();if(f.onclick){f.onclick()}else{e.show(h)}})},del:function(c){this.$el.find("#tab-"+c).remove();this.$el.find("#"+c).remove();if(this.first_tab==c){this.first_tab=null}if(this.first_tab!=null){this.show(this.first_tab)}if(this.list[c]){delete this.list[c]}if(this.size()<this.options.max){this.$el.find("#new-tab").show()}},delRemovable:function(){for(var c in this.list){this.del(c)}},show:function(c){this.$el.fadeIn("fast");this.visible=true;if(c){this.$el.find(".tab-element").removeClass("active");this.$el.find(".tab-pane").removeClass("active");this.$el.find("#tab-"+c).addClass("active");this.$el.find("#"+c).addClass("active")}},hide:function(){this.$el.fadeOut("fast");this.visible=false},hideOperation:function(c){this.$nav.find("#"+c).hide()},showOperation:function(c){this.$nav.find("#"+c).show()},setOperation:function(e,d){var c=this.$nav.find("#"+e);c.off("click");c.on("click",d)},title:function(e,d){var c=this.$el.find("#tab-title-text-"+e);if(d){c.html(d)}return c.html()},retitle:function(d){var c=0;for(var e in this.list){this.title(e,++c+": "+d)}},_template:function(c){return'<div class="ui-tabs tabbable tabs-left"><ul id="tab-navigation" class="tab-navigation nav nav-tabs"><div class="operations" style="float: right; margin-bottom: 4px;"></div></ul><div id="tab-content" class="tab-content"/></div>'},_template_tab_new:function(c){return'<li id="new-tab"><a href="javascript:void(0);"><i class="ui-tabs-add fa fa-plus-circle"/>'+c.title_new+"</a></li>"},_template_tab:function(d){var c='<li id="tab-'+d.id+'" class="tab-element"><a id="tab-title-link-'+d.id+'" title="" href="#'+d.id+'" data-original-title=""><span id="tab-title-text-'+d.id+'" class="tab-title-text">'+d.title+"</span>";if(d.ondel){c+='<i id="delete" class="ui-tabs-delete fa fa-minus-circle"/>'}c+="</a></li>";return c},_template_tab_content:function(c){return'<div id="'+c.id+'" class="tab-pane"/>'}});return{View:b}});
\ No newline at end of file
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{title_new:"",operations:null,onnew:null,min:null,max:null,onchange:null},initialize:function(e){this.visible=false;this.$nav=null;this.$content=null;this.first_tab=null;this.options=a.merge(e,this.optionsDefault);var c=$(this._template(this.options));this.$nav=c.find(".tab-navigation");this.$content=c.find(".tab-content");this.setElement(c);this.list={};var d=this;if(this.options.operations){$.each(this.options.operations,function(g,h){h.$el.prop("id",g);d.$nav.find(".operations").append(h.$el)})}if(this.options.onnew){var f=$(this._template_tab_new(this.options));this.$nav.append(f);f.tooltip({title:"Add a new tab",placement:"bottom",container:d.$el});f.on("click",function(g){f.tooltip("hide");d.options.onnew()})}},size:function(){return _.size(this.list)},current:function(){return this.$el.find(".tab-pane.active").attr("id")},add:function(f){var e=this;var h=f.id;var g=$(this._template_tab(f));var d=$(this._template_tab_content(f));this.list[h]=f.ondel?true:false;if(this.options.onnew){this.$nav.find("#new-tab").before(g)}else{this.$nav.append(g)}d.append(f.$el);this.$content.append(d);if(this.size()==1){g.addClass("active");d.addClass("active");this.first_tab=h}if(this.options.max&&this.size()>=this.options.max){this.$el.find("#new-tab").hide()}if(f.ondel){var c=g.find("#delete");c.tooltip({title:"Delete this tab",placement:"bottom",container:e.$el});c.on("click",function(){c.tooltip("destroy");e.$el.find(".tooltip").remove();f.ondel();return false})}g.on("click",function(i){i.preventDefault();if(f.onclick){f.onclick()}else{e.show(h)}})},del:function(c){this.$el.find("#tab-"+c).remove();this.$el.find("#"+c).remove();if(this.first_tab==c){this.first_tab=null}if(this.first_tab!=null){this.show(this.first_tab)}if(this.list[c]){delete this.list[c]}if(this.size()<this.options.max){this.$el.find("#new-tab").show()}},delRemovable:function(){for(var c in this.list){this.del(c)}},show:function(c){this.$el.fadeIn("fast");this.visible=true;if(c){this.$el.find(".tab-element").removeClass("active");this.$el.find(".tab-pane").removeClass("active");this.$el.find("#tab-"+c).addClass("active");this.$el.find("#"+c).addClass("active")}if(this.options.onchange){this.options.onchange(c)}},hide:function(){this.$el.fadeOut("fast");this.visible=false},hideOperation:function(c){this.$nav.find("#"+c).hide()},showOperation:function(c){this.$nav.find("#"+c).show()},setOperation:function(e,d){var c=this.$nav.find("#"+e);c.off("click");c.on("click",d)},title:function(e,d){var c=this.$el.find("#tab-title-text-"+e);if(d){c.html(d)}return c.html()},retitle:function(d){var c=0;for(var e in this.list){this.title(e,++c+": "+d)}},_template:function(c){return'<div class="ui-tabs tabbable tabs-left"><ul id="tab-navigation" class="tab-navigation nav nav-tabs"><div class="operations" style="float: right; margin-bottom: 4px;"></div></ul><div id="tab-content" class="tab-content"/></div>'},_template_tab_new:function(c){return'<li id="new-tab"><a href="javascript:void(0);"><i class="ui-tabs-add fa fa-plus-circle"/>'+c.title_new+"</a></li>"},_template_tab:function(d){var c='<li id="tab-'+d.id+'" class="tab-element"><a id="tab-title-link-'+d.id+'" title="" href="#'+d.id+'" data-original-title=""><span id="tab-title-text-'+d.id+'" class="tab-title-text">'+d.title+"</span>";if(d.ondel){c+='<i id="delete" class="ui-tabs-delete fa fa-minus-circle"/>'}c+="</a></li>";return c},_template_tab_content:function(c){return'<div id="'+c.id+'" class="tab-pane"/>'}});return{View:b}});
\ No newline at end of file
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -148,7 +148,7 @@
.form-control::-moz-placeholder{color:#999}
.form-control:-ms-input-placeholder{color:#999}
.form-control::-webkit-input-placeholder{color:#999}
-.form-control,.ui-input,.ui-textarea{display:block;width:100%;height:27px;padding:4px 10px;font-size:12px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;border:1px solid #aaa;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6)}
+.form-control,.ui-form-slider .ui-form-slider-text,.ui-input,.ui-textarea{display:block;width:100%;height:27px;padding:4px 10px;font-size:12px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;border:1px solid #aaa;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6)}
.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee}
textarea.form-control{height:auto}
.form-group{margin-bottom:15px}
@@ -1278,10 +1278,14 @@
.upload-ftp .upload-ftp-help{margin-bottom:10px}
.upload-ftp .upload-ftp-warning{text-align:center;margin-top:20px}
.upload-settings .upload-settings-cover{position:absolute;width:100%;height:100%;top:0px;left:0px;background:#fff;opacity:0.4;cursor:no-drop}
+.ui-form-slider .ui-form-slider-text{width:100px;float:left}
+.ui-form-slider .ui-form-slider-element{width:calc(100% - 110px);float:left;top:8px;left:10px}
.ui-radiobutton label{height:23px;line-height:1em}
.ui-table tbody{cursor:pointer}
.ui-table-plain tbody td{padding:5px 0px 5px 5px !important;border:none !important}
.ui-table-plain tbody{cursor:auto !important}
+.ui-table-form-section{border-left:solid 3px #ebd9b2;border-radius:5px;padding-left:5px}
+.ui-table-form-title-strong{font-weight:bold;padding-bottom:5px}
.ui-table-form-info{clear:both !important}
.ui-table-form-separator{font-weight:bold;font-size:0.9em}
.ui-label{font-weight:bold}
diff -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 -r f295824b2f9eb38d3de1121b63dc8a19f54ee0e9 static/style/src/less/ui.less
--- a/static/style/src/less/ui.less
+++ b/static/style/src/less/ui.less
@@ -1,3 +1,18 @@
+.ui-form-slider {
+ .ui-form-slider-text {
+ &:extend(.form-control);
+ width: 100px;
+ float: left;
+ }
+
+ .ui-form-slider-element {
+ width: ~'calc(100% - 110px)';
+ float: left;
+ top: 8px;
+ left: 10px;
+ }
+}
+
.ui-radiobutton {
label {
height: 23px;
@@ -30,6 +45,17 @@
}
}
+.ui-table-form-section {
+ border-left: solid 3px @form-heading-bg;
+ border-radius: 5px;
+ padding-left: 5px;
+}
+
+.ui-table-form-title-strong {
+ font-weight: bold;
+ padding-bottom: 5px;
+}
+
.ui-table-form-info {
&:extend(.toolParamHelp);
clear: both !important;
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/89b6cf2df4de/
Changeset: 89b6cf2df4de
User: jmchilton
Date: 2014-09-06 00:55:21
Summary: Tighten permissions for workflow invocations.
Sharing a workflow with a user was previously sufficient to grant access to all invocations of that workflow. This isn't a huge problem since the information potentially leaking out was limitted to invocation counts, various encoded ids, and update times. Still I think no information about invocations should be avaialble as a result of sharing a workflow - and upcoming changes to Galaxy will result in much more information being made available via the workflow invocation API.
Affected #: 3 files
diff -r 2604f7623fb459f3cbc6b3e30551e1a8ce0ea1b2 -r 89b6cf2df4deafc0740372bbeaff5ea0f954cd5d lib/galaxy/managers/workflows.py
--- /dev/null
+++ b/lib/galaxy/managers/workflows.py
@@ -0,0 +1,64 @@
+from galaxy import model
+from galaxy import exceptions
+
+
+class WorkflowsManager( object ):
+ """ Handle CRUD type operaitons related to workflows. More interesting
+ stuff regarding workflow execution, step sorting, etc... can be found in
+ the galaxy.workflow module.
+ """
+
+ def __init__( self, app ):
+ self.app = app
+
+ def check_security( self, trans, has_workflow, check_ownership=True, check_accessible=True):
+ """ check accessibility or ownership of workflows, storedworkflows, and
+ workflowinvocations. Throw an exception or returns True if user has
+ needed level of access.
+ """
+ if not check_ownership or check_accessible:
+ return True
+
+ # If given an invocation follow to workflow...
+ if isinstance( has_workflow, model.WorkflowInvocation ):
+ has_workflow = has_workflow.workflow
+
+ # stored workflow contains security stuff - follow that workflow to
+ # that unless given a stored workflow.
+ if hasattr( has_workflow, "stored_workflow" ):
+ stored_workflow = has_workflow.stored_workflow
+ else:
+ stored_workflow = has_workflow
+
+ if stored_workflow.user != trans.user and not trans.user_is_admin():
+ if check_ownership:
+ raise exceptions.ItemOwnershipException()
+ # else check_accessible...
+ if trans.sa_session.query( model.StoredWorkflowUserShareAssociation ).filter_by(user=trans.user, stored_workflow=stored_workflow ).count() == 0:
+ raise exceptions.ItemAccessibilityException()
+
+ return True
+
+ def get_invocation( self, trans, decoded_invocation_id ):
+ try:
+ workflow_invocation = trans.sa_session.query(
+ self.app.model.WorkflowInvocation
+ ).get( decoded_invocation_id )
+ except Exception:
+ raise exceptions.ObjectNotFound()
+ self.check_security( trans, workflow_invocation, check_ownership=True, check_accessible=False )
+ return workflow_invocation
+
+ def build_invocations_query( self, trans, decoded_stored_workflow_id ):
+ try:
+ stored_workflow = trans.sa_session.query(
+ self.app.model.StoredWorkflow
+ ).get( decoded_stored_workflow_id )
+ except Exception:
+ raise exceptions.ObjectNotFound()
+ self.check_security( trans, stored_workflow, check_ownership=True, check_accessible=False )
+ return trans.sa_session.query(
+ model.WorkflowInvocation
+ ).filter_by(
+ workflow_id=stored_workflow.latest_workflow_id
+ )
diff -r 2604f7623fb459f3cbc6b3e30551e1a8ce0ea1b2 -r 89b6cf2df4deafc0740372bbeaff5ea0f954cd5d lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -10,6 +10,7 @@
from galaxy import exceptions, util
from galaxy.model.item_attrs import UsesAnnotations
from galaxy.managers import histories
+from galaxy.managers import workflows
from galaxy.web import _future_expose_api as expose_api
from galaxy.web.base.controller import BaseAPIController, url_for, UsesStoredWorkflowMixin
from galaxy.web.base.controller import UsesHistoryMixin
@@ -26,6 +27,7 @@
def __init__( self, app ):
super( BaseAPIController, self ).__init__( app )
self.history_manager = histories.HistoryManager()
+ self.workflow_manager = workflows.WorkflowsManager( app )
@expose_api
def index(self, trans, **kwd):
@@ -377,15 +379,8 @@
:raises: exceptions.MessageException, exceptions.ObjectNotFound
"""
- try:
- stored_workflow = trans.sa_session.query(self.app.model.StoredWorkflow).get(trans.security.decode_id(workflow_id))
- except Exception:
- raise exceptions.ObjectNotFound()
- # check to see if user has permissions to selected workflow
- if stored_workflow.user != trans.user and not trans.user_is_admin():
- if trans.sa_session.query(trans.app.model.StoredWorkflowUserShareAssociation).filter_by(user=trans.user, stored_workflow=stored_workflow).count() == 0:
- raise exceptions.ItemOwnershipException()
- results = trans.sa_session.query(self.app.model.WorkflowInvocation).filter_by(workflow_id=stored_workflow.latest_workflow_id)
+ decoded_stored_workflow_invocation_id = self.__decode_id( trans, workflow_id )
+ results = self.workflow_manager.build_invocations_query( trans, decoded_stored_workflow_invocation_id )
out = []
for r in results:
out.append( self.encode_all_ids( trans, r.to_dict(), True) )
@@ -405,20 +400,10 @@
:raises: exceptions.MessageException, exceptions.ObjectNotFound
"""
-
- try:
- stored_workflow = trans.sa_session.query(self.app.model.StoredWorkflow).get(trans.security.decode_id(workflow_id))
- except Exception:
- raise exceptions.ObjectNotFound()
- # check to see if user has permissions to selected workflow
- if stored_workflow.user != trans.user and not trans.user_is_admin():
- if trans.sa_session.query(trans.app.model.StoredWorkflowUserShareAssociation).filter_by(user=trans.user, stored_workflow=stored_workflow).count() == 0:
- raise exceptions.ItemOwnershipException()
- results = trans.sa_session.query(self.app.model.WorkflowInvocation).filter_by(workflow_id=stored_workflow.latest_workflow_id)
- results = results.filter_by(id=trans.security.decode_id(usage_id))
- out = results.first()
- if out is not None:
- return self.encode_all_ids( trans, out.to_dict('element'), True)
+ decoded_workflow_invocation_id = self.__decode_id( trans, usage_id )
+ workflow_invocation = self.workflow_manager.get_invocation( trans, decoded_workflow_invocation_id )
+ if workflow_invocation:
+ return self.encode_all_ids( trans, workflow_invocation.to_dict('element'), True)
return None
def __get_stored_accessible_workflow( self, trans, workflow_id ):
diff -r 2604f7623fb459f3cbc6b3e30551e1a8ce0ea1b2 -r 89b6cf2df4deafc0740372bbeaff5ea0f954cd5d test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -506,22 +506,16 @@
self._assert_has_keys( pja, "action_type", "output_name", "action_arguments" )
@skip_without_tool( "cat1" )
+ def test_only_own_invocations_accessible( self ):
+ workflow_id, usage = self._run_workflow_once_get_invocation( "test_usage")
+ with self._different_user():
+ usage_details_response = self._get( "workflows/%s/usage/%s" % ( workflow_id, usage[ "id" ] ) )
+ self._assert_status_code_is( usage_details_response, 403 )
+
+ @skip_without_tool( "cat1" )
def test_invocation_usage( self ):
- workflow = self.workflow_populator.load_workflow( name="test_usage" )
- workflow_request, history_id = self._setup_workflow_run( workflow )
- workflow_id = workflow_request[ "workflow_id" ]
- response = self._get( "workflows/%s/usage" % workflow_id )
- self._assert_status_code_is( response, 200 )
- assert len( response.json() ) == 0
- run_workflow_response = self._post( "workflows", data=workflow_request )
- self._assert_status_code_is( run_workflow_response, 200 )
-
- response = self._get( "workflows/%s/usage" % workflow_id )
- self._assert_status_code_is( response, 200 )
- usages = response.json()
- assert len( usages ) == 1
-
- usage_details_response = self._get( "workflows/%s/usage/%s" % ( workflow_id, usages[ 0 ][ "id" ] ) )
+ workflow_id, usage = self._run_workflow_once_get_invocation( "test_usage")
+ usage_details_response = self._get( "workflows/%s/usage/%s" % ( workflow_id, usage[ "id" ] ) )
self._assert_status_code_is( usage_details_response, 200 )
usage_details = usage_details_response.json()
# Assert some high-level things about the structure of data returned.
@@ -544,6 +538,22 @@
# renamed to 'the_new_name'.
assert "the_new_name" in map( lambda hda: hda[ "name" ], contents )
+ def _run_workflow_once_get_invocation( self, name ):
+ workflow = self.workflow_populator.load_workflow( name=name )
+ workflow_request, history_id = self._setup_workflow_run( workflow )
+ workflow_id = workflow_request[ "workflow_id" ]
+ response = self._get( "workflows/%s/usage" % workflow_id )
+ self._assert_status_code_is( response, 200 )
+ assert len( response.json() ) == 0
+ run_workflow_response = self._post( "workflows", data=workflow_request )
+ self._assert_status_code_is( run_workflow_response, 200 )
+
+ response = self._get( "workflows/%s/usage" % workflow_id )
+ self._assert_status_code_is( response, 200 )
+ usages = response.json()
+ assert len( usages ) == 1
+ return workflow_id, usages[ 0 ]
+
def _setup_workflow_run( self, workflow, inputs_by='step_id', history_id=None ):
uploaded_workflow_id = self.workflow_populator.create_workflow( workflow )
if not history_id:
https://bitbucket.org/galaxy/galaxy-central/commits/14953d248e9b/
Changeset: 14953d248e9b
User: jmchilton
Date: 2014-09-06 00:55:21
Summary: Update workflow invocation to_dict for recent collection workflow changes.
There may now be multiple WorkflowInvocationSteps for each WorkflowStep for steps that are mapped over collections - so to_dict creating a dictionary of this information indexed on order step is problematic because only one WorkflowInvocationStep will be represented per step. Instead now just returning a big list of all of the invocations - which contains all of the same information. This is a backward incompatible API change for the workflow invocation API.
Also update the input mapping stuff with logic for dealing with data collection inputs.
Affected #: 2 files
diff -r 89b6cf2df4deafc0740372bbeaff5ea0f954cd5d -r 14953d248e9b404589c1e81494517f47c1004e49 lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -3081,20 +3081,22 @@
def to_dict( self, view='collection', value_mapper=None ):
rval = super( WorkflowInvocation, self ).to_dict( view=view, value_mapper=value_mapper )
if view == 'element':
- steps = {}
+ steps = []
for step in self.steps:
v = step.to_dict()
- steps[str(v['order_index'])] = v
+ steps.append( v )
rval['steps'] = steps
inputs = {}
for step in self.steps:
if step.workflow_step.type == 'tool':
for step_input in step.workflow_step.input_connections:
- if step_input.output_step.type == 'data_input':
+ output_step_type = step_input.output_step.type
+ if output_step_type in [ 'data_input', 'data_collection_input' ]:
+ src = "hda" if output_step_type == 'data_input' else 'hdca'
for job_input in step.job.input_datasets:
if job_input.name == step_input.input_name:
- inputs[str(step_input.output_step.order_index)] = { "id": job_input.dataset_id, "src": "hda"}
+ inputs[str(step_input.output_step.order_index)] = { "id": job_input.dataset_id, "src": src }
rval['inputs'] = inputs
return rval
diff -r 89b6cf2df4deafc0740372bbeaff5ea0f954cd5d -r 14953d248e9b404589c1e81494517f47c1004e49 test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -520,8 +520,8 @@
usage_details = usage_details_response.json()
# Assert some high-level things about the structure of data returned.
self._assert_has_keys( usage_details, "inputs", "steps" )
- for step in usage_details[ "steps" ].values():
- self._assert_has_keys( step, "workflow_step_id", "order_index" )
+ for step in usage_details[ "steps" ]:
+ self._assert_has_keys( step, "workflow_step_id", "order_index", "id" )
@skip_without_tool( "cat1" )
def test_post_job_action( self ):
https://bitbucket.org/galaxy/galaxy-central/commits/54efa2c365f4/
Changeset: 54efa2c365f4
User: jmchilton
Date: 2014-09-06 00:55:21
Summary: Refactoring out methods usable for downstream work on scheduling.
Affected #: 2 files
diff -r 14953d248e9b404589c1e81494517f47c1004e49 -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -383,7 +383,7 @@
results = self.workflow_manager.build_invocations_query( trans, decoded_stored_workflow_invocation_id )
out = []
for r in results:
- out.append( self.encode_all_ids( trans, r.to_dict(), True) )
+ out.append( self.__encode_invocation( trans, r ) )
return out
@expose_api
@@ -403,7 +403,7 @@
decoded_workflow_invocation_id = self.__decode_id( trans, usage_id )
workflow_invocation = self.workflow_manager.get_invocation( trans, decoded_workflow_invocation_id )
if workflow_invocation:
- return self.encode_all_ids( trans, workflow_invocation.to_dict('element'), True)
+ return self.__encode_invocation( trans, workflow_invocation )
return None
def __get_stored_accessible_workflow( self, trans, workflow_id ):
@@ -435,6 +435,13 @@
raise exceptions.ObjectNotFound( "No such workflow found." )
return stored_workflow
+ def __encode_invocation( self, trans, invocation, view="element" ):
+ return self.encode_all_ids(
+ trans,
+ invocation.to_dict( view ),
+ True
+ )
+
def __decode_id( self, trans, workflow_id, model_type="workflow" ):
try:
return trans.security.decode_id( workflow_id )
diff -r 14953d248e9b404589c1e81494517f47c1004e49 -r 54efa2c365f4e2c867d56e44fb202ec2262e1122 test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -515,9 +515,7 @@
@skip_without_tool( "cat1" )
def test_invocation_usage( self ):
workflow_id, usage = self._run_workflow_once_get_invocation( "test_usage")
- usage_details_response = self._get( "workflows/%s/usage/%s" % ( workflow_id, usage[ "id" ] ) )
- self._assert_status_code_is( usage_details_response, 200 )
- usage_details = usage_details_response.json()
+ usage_details = self._invocation_details( workflow_id, usage[ "id" ] )
# Assert some high-level things about the structure of data returned.
self._assert_has_keys( usage_details, "inputs", "steps" )
for step in usage_details[ "steps" ]:
@@ -538,6 +536,12 @@
# renamed to 'the_new_name'.
assert "the_new_name" in map( lambda hda: hda[ "name" ], contents )
+ def _invocation_details( self, workflow_id, invocation_id ):
+ invocation_details_response = self._get( "workflows/%s/usage/%s" % ( workflow_id, invocation_id ) )
+ self._assert_status_code_is( invocation_details_response, 200 )
+ invocation_details = invocation_details_response.json()
+ return invocation_details
+
def _run_workflow_once_get_invocation( self, name ):
workflow = self.workflow_populator.load_workflow( name=name )
workflow_request, history_id = self._setup_workflow_run( workflow )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/25656020ffb4/
Changeset: 25656020ffb4
Branch: job_admin
User: dannon
Date: 2014-09-05 21:44:56
Summary: Close feature branch
Affected #: 0 files
https://bitbucket.org/galaxy/galaxy-central/commits/62c2f16bcdeb/
Changeset: 62c2f16bcdeb
Branch: update-hist-export
User: dannon
Date: 2014-09-05 21:45:04
Summary: Close feature branch
Affected #: 0 files
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: dannon: Merged in kellrott/galaxy-farm/update-hist-export (pull request #472)
by commits-noreply@bitbucket.org 05 Sep '14
by commits-noreply@bitbucket.org 05 Sep '14
05 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/2604f7623fb4/
Changeset: 2604f7623fb4
User: dannon
Date: 2014-09-05 21:43:36
Summary: Merged in kellrott/galaxy-farm/update-hist-export (pull request #472)
Updating history import export
Affected #: 8 files
diff -r 6866bba8b7e28e25820ae0ca73c9159b91bd922e -r 2604f7623fb459f3cbc6b3e30551e1a8ce0ea1b2 lib/galaxy/tools/imp_exp/__init__.py
--- a/lib/galaxy/tools/imp_exp/__init__.py
+++ b/lib/galaxy/tools/imp_exp/__init__.py
@@ -3,6 +3,7 @@
import logging
import tempfile
import json
+import datetime
from galaxy import model
from galaxy.tools.parameters.basic import UnvalidatedValue
from galaxy.web.framework.helpers import to_unicode
@@ -134,6 +135,11 @@
datasets_attrs_file_name = os.path.join( archive_dir, 'datasets_attrs.txt')
datasets_attr_str = read_file_contents( datasets_attrs_file_name )
datasets_attrs = from_json_string( datasets_attr_str )
+
+ if os.path.exists( datasets_attrs_file_name + ".provenance" ):
+ provenance_attr_str = read_file_contents( datasets_attrs_file_name + ".provenance" )
+ provenance_attrs = from_json_string( provenance_attr_str )
+ datasets_attrs += provenance_attrs
# Get counts of how often each dataset file is used; a file can
# be linked to multiple dataset objects (HDAs).
@@ -162,7 +168,14 @@
history=new_history,
create_dataset=True,
sa_session=self.sa_session )
- hda.state = hda.states.OK
+ if 'uuid' in dataset_attrs:
+ hda.dataset.uuid = dataset_attrs["uuid"]
+ if dataset_attrs.get('exported', True) == False:
+ hda.state = hda.states.DISCARDED
+ hda.deleted = True
+ hda.purged = True
+ else:
+ hda.state = hda.states.OK
self.sa_session.add( hda )
self.sa_session.flush()
new_history.add_dataset( hda, genome_build=None )
@@ -171,17 +184,18 @@
#permissions = trans.app.security_agent.history_get_default_permissions( new_history )
#trans.app.security_agent.set_all_dataset_permissions( hda.dataset, permissions )
self.sa_session.flush()
-
- # Do security check and move/copy dataset data.
- temp_dataset_file_name = \
- os.path.abspath( os.path.join( archive_dir, dataset_attrs['file_name'] ) )
- if not file_in_dir( temp_dataset_file_name, os.path.join( archive_dir, "datasets" ) ):
- raise Exception( "Invalid dataset path: %s" % temp_dataset_file_name )
- if datasets_usage_counts[ temp_dataset_file_name ] == 1:
- shutil.move( temp_dataset_file_name, hda.file_name )
- else:
- datasets_usage_counts[ temp_dataset_file_name ] -= 1
- shutil.copyfile( temp_dataset_file_name, hda.file_name )
+ if dataset_attrs.get('exported', True) == True:
+ # Do security check and move/copy dataset data.
+ temp_dataset_file_name = \
+ os.path.abspath( os.path.join( archive_dir, dataset_attrs['file_name'] ) )
+ if not file_in_dir( temp_dataset_file_name, os.path.join( archive_dir, "datasets" ) ):
+ raise Exception( "Invalid dataset path: %s" % temp_dataset_file_name )
+ if datasets_usage_counts[ temp_dataset_file_name ] == 1:
+ shutil.move( temp_dataset_file_name, hda.file_name )
+ else:
+ datasets_usage_counts[ temp_dataset_file_name ] -= 1
+ shutil.copyfile( temp_dataset_file_name, hda.file_name )
+ hda.dataset.set_total_size() #update the filesize record in the database
# Set tags, annotations.
if user:
@@ -225,10 +239,21 @@
# TODO: set session?
# imported_job.session = trans.get_galaxy_session().id
imported_job.history = new_history
+ imported_job.imported = True
imported_job.tool_id = job_attrs[ 'tool_id' ]
imported_job.tool_version = job_attrs[ 'tool_version' ]
imported_job.set_state( job_attrs[ 'state' ] )
- imported_job.imported = True
+ imported_job.info = job_attrs.get('info', None)
+ imported_job.exit_code = job_attrs.get('exit_code', None)
+ imported_job.traceback = job_attrs.get('traceback', None)
+ imported_job.stdout = job_attrs.get('stdout', None)
+ imported_job.stderr = job_attrs.get('stderr', None)
+ imported_job.command_line = job_attrs.get('command_line', None)
+ try:
+ imported_job.create_time = datetime.datetime.strptime(job_attrs["create_time"], "%Y-%m-%dT%H:%M:%S.%f")
+ imported_job.update_time = datetime.datetime.strptime(job_attrs["update_time"], "%Y-%m-%dT%H:%M:%S.%f")
+ except:
+ pass
self.sa_session.add( imported_job )
self.sa_session.flush()
@@ -266,6 +291,16 @@
if output_hda:
imported_job.add_output_dataset( output_hda.name, output_hda )
+ # Connect jobs to input datasets.
+ if 'input_mapping' in job_attrs:
+ for input_name, input_hid in job_attrs[ 'input_mapping' ].items():
+ #print "%s job has input dataset %i" % (imported_job.id, input_hid)
+ input_hda = self.sa_session.query( model.HistoryDatasetAssociation ) \
+ .filter_by( history=new_history, hid=input_hid ).first()
+ if input_hda:
+ imported_job.add_input_dataset( input_name, input_hda )
+
+
self.sa_session.flush()
# Done importing.
@@ -323,7 +358,7 @@
def default( self, obj ):
""" Encode an HDA, default encoding for everything else. """
if isinstance( obj, trans.app.model.HistoryDatasetAssociation ):
- return {
+ rval = {
"__HistoryDatasetAssociation__": True,
"create_time": obj.create_time.__str__(),
"update_time": obj.update_time.__str__(),
@@ -339,9 +374,17 @@
"deleted": obj.deleted,
"visible": obj.visible,
"file_name": obj.file_name,
+ "uuid" : ( lambda uuid: str( uuid ) if uuid else None )( obj.dataset.uuid ),
"annotation": to_unicode( getattr( obj, 'annotation', '' ) ),
"tags": get_item_tag_dict( obj ),
}
+ if not obj.visible and not include_hidden:
+ rval['exported'] = False
+ elif obj.deleted and not include_deleted:
+ rval['exported'] = False
+ else:
+ rval['exported'] = True
+ return rval
if isinstance( obj, UnvalidatedValue ):
return obj.__str__()
return json.JSONEncoder.default( self, obj )
@@ -374,19 +417,23 @@
datasets = self.get_history_datasets( trans, history )
included_datasets = []
datasets_attrs = []
+ provenance_attrs = []
for dataset in datasets:
- if not dataset.visible and not include_hidden:
- continue
- if dataset.deleted and not include_deleted:
- continue
dataset.annotation = self.get_item_annotation_str( trans.sa_session, history.user, dataset )
- datasets_attrs.append( dataset )
- included_datasets.append( dataset )
+ if (not dataset.visible and not include_hidden) or (dataset.deleted and not include_deleted):
+ provenance_attrs.append( dataset )
+ else:
+ datasets_attrs.append( dataset )
+ included_datasets.append( dataset )
datasets_attrs_filename = tempfile.NamedTemporaryFile( dir=temp_output_dir ).name
datasets_attrs_out = open( datasets_attrs_filename, 'w' )
datasets_attrs_out.write( to_json_string( datasets_attrs, cls=HistoryDatasetAssociationEncoder ) )
datasets_attrs_out.close()
jeha.datasets_attrs_filename = datasets_attrs_filename
+
+ provenance_attrs_out = open( datasets_attrs_filename + ".provenance", 'w' )
+ provenance_attrs_out.write( to_json_string( provenance_attrs, cls=HistoryDatasetAssociationEncoder ) )
+ provenance_attrs_out.close()
#
# Write jobs attributes file.
@@ -422,6 +469,15 @@
job_attrs[ 'tool_id' ] = job.tool_id
job_attrs[ 'tool_version' ] = job.tool_version
job_attrs[ 'state' ] = job.state
+ job_attrs[ 'info' ] = job.info
+ job_attrs[ 'traceback' ] = job.traceback
+ job_attrs[ 'command_line' ] = job.command_line
+ job_attrs[ 'stderr' ] = job.stderr
+ job_attrs[ 'stdout' ] = job.stdout
+ job_attrs[ 'exit_code' ] = job.exit_code
+ job_attrs[ 'create_time' ] = job.create_time.isoformat()
+ job_attrs[ 'update_time' ] = job.update_time.isoformat()
+
# Get the job's parameters
try:
@@ -438,11 +494,14 @@
# -- Get input, output datasets. --
input_datasets = []
+ input_mapping = {}
for assoc in job.input_datasets:
# Optional data inputs will not have a dataset.
if assoc.dataset:
input_datasets.append( assoc.dataset.hid )
+ input_mapping[assoc.name] = assoc.dataset.hid
job_attrs[ 'input_datasets' ] = input_datasets
+ job_attrs[ 'input_mapping'] = input_mapping
output_datasets = [ assoc.dataset.hid for assoc in job.output_datasets ]
job_attrs[ 'output_datasets' ] = output_datasets
diff -r 6866bba8b7e28e25820ae0ca73c9159b91bd922e -r 2604f7623fb459f3cbc6b3e30551e1a8ce0ea1b2 lib/galaxy/tools/imp_exp/export_history.py
--- a/lib/galaxy/tools/imp_exp/export_history.py
+++ b/lib/galaxy/tools/imp_exp/export_history.py
@@ -44,12 +44,13 @@
# Add datasets to archive and update dataset attributes.
# TODO: security check to ensure that files added are in Galaxy dataset directory?
for dataset_attrs in datasets_attrs:
- dataset_file_name = dataset_attrs[ 'file_name' ] # Full file name.
- dataset_archive_name = os.path.join( 'datasets',
- get_dataset_filename( dataset_attrs[ 'name' ], dataset_attrs[ 'extension' ] ) )
- history_archive.add( dataset_file_name, arcname=dataset_archive_name )
- # Update dataset filename to be archive name.
- dataset_attrs[ 'file_name' ] = dataset_archive_name
+ if dataset_attrs['exported']:
+ dataset_file_name = dataset_attrs[ 'file_name' ] # Full file name.
+ dataset_archive_name = os.path.join( 'datasets',
+ get_dataset_filename( dataset_attrs[ 'name' ], dataset_attrs[ 'extension' ] ) )
+ history_archive.add( dataset_file_name, arcname=dataset_archive_name )
+ # Update dataset filename to be archive name.
+ dataset_attrs[ 'file_name' ] = dataset_archive_name
# Rewrite dataset attributes file.
datasets_attrs_out = open( datasets_attrs_file, 'w' )
@@ -59,6 +60,8 @@
# Finish archive.
history_archive.add( history_attrs_file, arcname="history_attrs.txt" )
history_archive.add( datasets_attrs_file, arcname="datasets_attrs.txt" )
+ if os.path.exists( datasets_attrs_file + ".provenance" ):
+ history_archive.add( datasets_attrs_file + ".provenance", arcname="datasets_attrs.txt.provenance" )
history_archive.add( jobs_attrs_file, arcname="jobs_attrs.txt" )
history_archive.close()
diff -r 6866bba8b7e28e25820ae0ca73c9159b91bd922e -r 2604f7623fb459f3cbc6b3e30551e1a8ce0ea1b2 lib/galaxy/webapps/galaxy/api/histories.py
--- a/lib/galaxy/webapps/galaxy/api/histories.py
+++ b/lib/galaxy/webapps/galaxy/api/histories.py
@@ -312,6 +312,8 @@
check_ownership=False, check_accessible=True )
jeha = history.latest_export
up_to_date = jeha and jeha.up_to_date
+ if 'force' in kwds:
+ up_to_date = False #Temp hack to force rebuild everytime during dev
if not up_to_date:
# Need to create new JEHA + job.
gzip = kwds.get( "gzip", True )
diff -r 6866bba8b7e28e25820ae0ca73c9159b91bd922e -r 2604f7623fb459f3cbc6b3e30551e1a8ce0ea1b2 lib/galaxy/webapps/galaxy/controllers/history.py
--- a/lib/galaxy/webapps/galaxy/controllers/history.py
+++ b/lib/galaxy/webapps/galaxy/controllers/history.py
@@ -1118,7 +1118,7 @@
#TODO: used in this file and index.mako
@web.expose
- def export_archive( self, trans, id=None, gzip=True, include_hidden=False, include_deleted=False ):
+ def export_archive( self, trans, id=None, gzip=True, include_hidden=False, include_deleted=False, preview=False ):
""" Export a history to an archive. """
#
# Get history to export.
@@ -1139,7 +1139,13 @@
jeha = history.latest_export
if jeha and jeha.up_to_date:
if jeha.ready:
- return self.serve_ready_history_export( trans, jeha )
+ if preview:
+ url = url_for( controller='history', action="export_archive", id=id, qualified=True )
+ return trans.show_message( "History Ready: '%(n)s'. Use this link to download \
+ the archive or import it to another Galaxy server: \
+ <a href='%(u)s'>%(u)s</a>" % ( { 'n' : history.name, 'u' : url } ) )
+ else:
+ return self.serve_ready_history_export( trans, jeha )
elif jeha.preparing:
return trans.show_message( "Still exporting history %(n)s; please check back soon. Link: <a href='%(s)s'>%(s)s</a>" \
% ( { 'n' : history.name, 's' : url_for( controller='history', action="export_archive", id=id, qualified=True ) } ) )
diff -r 6866bba8b7e28e25820ae0ca73c9159b91bd922e -r 2604f7623fb459f3cbc6b3e30551e1a8ce0ea1b2 templates/show_params.mako
--- a/templates/show_params.mako
+++ b/templates/show_params.mako
@@ -107,6 +107,7 @@
<tbody><%
encoded_hda_id = trans.security.encode_id( hda.id )
+ encoded_history_id = trans.security.encode_id( hda.history_id )
%><tr><td>Name:</td><td>${hda.name | h}</td></tr><tr><td>Created:</td><td>${hda.create_time.strftime(trans.app.config.pretty_datetime_format)}</td></tr>
@@ -120,6 +121,7 @@
<tr><td>Tool Standard Error:</td><td><a href="${h.url_for( controller='dataset', action='stderr', dataset_id=encoded_hda_id )}">stderr</a></td></tr><tr><td>Tool Exit Code:</td><td>${job.exit_code | h}</td></tr><tr><td>API ID:</td><td>${encoded_hda_id}</td></tr>
+ <tr><td>History ID:</td><td>${encoded_history_id}</td></tr>
%if hda.dataset.uuid:
<tr><td>UUID:</td><td>${hda.dataset.uuid}</td></tr>
%endif
diff -r 6866bba8b7e28e25820ae0ca73c9159b91bd922e -r 2604f7623fb459f3cbc6b3e30551e1a8ce0ea1b2 templates/webapps/galaxy/root/index.mako
--- a/templates/webapps/galaxy/root/index.mako
+++ b/templates/webapps/galaxy/root/index.mako
@@ -105,7 +105,7 @@
galaxy_main.location = "${h.url_for( controller='history', action='citations' )}";
},
"${_("Export to File")}": function() {
- galaxy_main.location = "${h.url_for( controller='history', action='export_archive' )}";
+ galaxy_main.location = "${h.url_for( controller='history', action='export_archive', preview=True )}";
},
"${_("Delete")}": function() {
if ( confirm( "Really delete the current history?" ) ) {
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
05 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/7d7048ac4f87/
Changeset: 7d7048ac4f87
Branch: coding_snp_2bit_locfile
User: dannon
Date: 2014-09-05 21:26:10
Summary: Close feature branch
Affected #: 0 files
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: dannon: Merged in lance_parsons/galaxy-central-pull-requests/coding_snp_2bit_locfile (pull request #487)
by commits-noreply@bitbucket.org 05 Sep '14
by commits-noreply@bitbucket.org 05 Sep '14
05 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/6866bba8b7e2/
Changeset: 6866bba8b7e2
User: dannon
Date: 2014-09-05 21:25:38
Summary: Merged in lance_parsons/galaxy-central-pull-requests/coding_snp_2bit_locfile (pull request #487)
Allow specification of complete filename of 2bit files in codingSnps.pl
Affected #: 1 file
diff -r 92238b01c946c03b93f4b77d0729b24684940c89 -r 6866bba8b7e28e25820ae0ca73c9159b91bd922e tools/evolution/codingSnps.pl
--- a/tools/evolution/codingSnps.pl
+++ b/tools/evolution/codingSnps.pl
@@ -564,6 +564,8 @@
if ($nibDir eq 'Galaxy') {
print STDERR "Failed to find sequence directory in locfile $locFile\n";
}
- $nibDir .= "/$build.2bit"; #we want full path and filename
+ # lparsons: allow specification of full filename in loc file for greater felxibility
+ unless ($nibDir =~ /(.*)\.2bit$/) { $nibDir .= "/$build.2bit"; }
+ #$nibDir .= "/$build.2bit"; #we want full path and filename
}
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/b04e01c4b78a/
Changeset: b04e01c4b78a
Branch: coding_snp_2bit_locfile
User: lance_parsons
Date: 2014-09-04 22:02:54
Summary: Allow specification of complete filename of 2bit files in codingSnps.pl
Affected #: 1 file
diff -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 -r b04e01c4b78ac308414c5047327713899195ce9d tools/evolution/codingSnps.pl
--- a/tools/evolution/codingSnps.pl
+++ b/tools/evolution/codingSnps.pl
@@ -564,6 +564,8 @@
if ($nibDir eq 'Galaxy') {
print STDERR "Failed to find sequence directory in locfile $locFile\n";
}
- $nibDir .= "/$build.2bit"; #we want full path and filename
+ # lparsons: allow specification of full filename in loc file for greater felxibility
+ unless ($nibDir =~ /(.*)\.2bit/) { $nibDir .= "/$build.2bit"; }
+ #$nibDir .= "/$build.2bit"; #we want full path and filename
}
https://bitbucket.org/galaxy/galaxy-central/commits/ecaabded6e50/
Changeset: ecaabded6e50
Branch: coding_snp_2bit_locfile
User: lance_parsons
Date: 2014-09-05 16:55:38
Summary: Made regex more specific
Affected #: 1 file
diff -r b04e01c4b78ac308414c5047327713899195ce9d -r ecaabded6e5027bb92ee5559e226a82dbde192e3 tools/evolution/codingSnps.pl
--- a/tools/evolution/codingSnps.pl
+++ b/tools/evolution/codingSnps.pl
@@ -565,7 +565,7 @@
print STDERR "Failed to find sequence directory in locfile $locFile\n";
}
# lparsons: allow specification of full filename in loc file for greater felxibility
- unless ($nibDir =~ /(.*)\.2bit/) { $nibDir .= "/$build.2bit"; }
+ unless ($nibDir =~ /(.*)\.2bit$/) { $nibDir .= "/$build.2bit"; }
#$nibDir .= "/$build.2bit"; #we want full path and filename
}
https://bitbucket.org/galaxy/galaxy-central/commits/6866bba8b7e2/
Changeset: 6866bba8b7e2
User: dannon
Date: 2014-09-05 21:25:38
Summary: Merged in lance_parsons/galaxy-central-pull-requests/coding_snp_2bit_locfile (pull request #487)
Allow specification of complete filename of 2bit files in codingSnps.pl
Affected #: 1 file
diff -r 92238b01c946c03b93f4b77d0729b24684940c89 -r 6866bba8b7e28e25820ae0ca73c9159b91bd922e tools/evolution/codingSnps.pl
--- a/tools/evolution/codingSnps.pl
+++ b/tools/evolution/codingSnps.pl
@@ -564,6 +564,8 @@
if ($nibDir eq 'Galaxy') {
print STDERR "Failed to find sequence directory in locfile $locFile\n";
}
- $nibDir .= "/$build.2bit"; #we want full path and filename
+ # lparsons: allow specification of full filename in loc file for greater felxibility
+ unless ($nibDir =~ /(.*)\.2bit$/) { $nibDir .= "/$build.2bit"; }
+ #$nibDir .= "/$build.2bit"; #we want full path and filename
}
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0119b0d6befb/
Changeset: 0119b0d6befb
User: jmchilton
Date: 2014-09-05 21:19:18
Summary: More discovered dataset testing tweaks.
Affected #: 2 files
diff -r 77e633e926043524691dcc193f3e7d59809d06a8 -r 0119b0d6befbb77f7ae177ccf036109d97fa53f1 lib/galaxy/tools/parameters/output_collect.py
--- a/lib/galaxy/tools/parameters/output_collect.py
+++ b/lib/galaxy/tools/parameters/output_collect.py
@@ -186,7 +186,8 @@
return self.pattern.replace( DATASET_ID_TOKEN, token_replacement )
def match( self, dataset_instance, filename ):
- re_match = re.match( self.pattern_for_dataset( dataset_instance ), filename )
+ pattern = self.pattern_for_dataset( dataset_instance )
+ re_match = re.match( pattern, filename )
match_object = None
if re_match:
match_object = CollectedDatasetMatch( re_match, self )
diff -r 77e633e926043524691dcc193f3e7d59809d06a8 -r 0119b0d6befbb77f7ae177ccf036109d97fa53f1 test/functional/tools/multi_output_configured.xml
--- a/test/functional/tools/multi_output_configured.xml
+++ b/test/functional/tools/multi_output_configured.xml
@@ -1,4 +1,4 @@
-<tool id="multi_output_configured" name="Multi_Output_Configured" description="multi_output_configured" force_history_refresh="True" version="0.1.0">
+<tool id="multi_output_configured" name="Multi_Output_Configured" description="multi_output_configured" version="0.1.0"><command>
echo "Hello" > $report;
mkdir subdir1;
@@ -12,6 +12,9 @@
echo "Foo" > subdir3/Foo;
echo "mapped reads" > split_bam_.MAPPED.bam;
echo "unmapped reads" > split_bam_.UNMAPPED.bam;
+ echo "1" > sample1.report.tsv;
+ echo "2" > sample2.report.tsv;
+ echo "3" > sample3.report.tsv;
</command><inputs><param name="num_param" type="integer" value="7" />
@@ -23,6 +26,7 @@
<discover_datasets pattern="CUSTOM_(?P<designation>.+)\.(?P<ext>.+)" directory="subdir2" visible="true" /><discover_datasets pattern="__designation__" directory="subdir3" ext="input" visible="true" /><discover_datasets pattern="split_bam_\.(?P<designation>([A-Z-])\w+)\.bam" ext="txt" visible="true" />
+ <discover_datasets pattern="(?P<designation>.+)\.report\.tsv" ext="tabular" visible="true" /></data></outputs><tests>
@@ -54,6 +58,9 @@
<discovered_dataset designation="UNMAPPED" ftype="txt"><assert_contents><has_line line="unmapped reads" /></assert_contents></discovered_dataset>
+ <discovered_dataset designation="sample1" ftype="tabular">
+ <assert_contents><has_line line="1" /></assert_contents>
+ </discovered_dataset></output></test></tests>
https://bitbucket.org/galaxy/galaxy-central/commits/8b450e9886e1/
Changeset: 8b450e9886e1
User: jmchilton
Date: 2014-09-05 21:19:18
Summary: Switch default test interactor to API.
Has dozens of advantages and the main tool shed is now running tests against the API and no one has complained.
If you find regressions in test cases as part of this switch, please report them and set interactor="twill" on your test cases to revert to the old runner until the problem is being fixed. Eventually the twill interactor for tool functional tests will be eliminated all together.
Affected #: 1 file
diff -r 0119b0d6befbb77f7ae177ccf036109d97fa53f1 -r 8b450e9886e1e8d62201bf307d79e2f8e3458fbd lib/galaxy/tools/test.py
--- a/lib/galaxy/tools/test.py
+++ b/lib/galaxy/tools/test.py
@@ -14,7 +14,7 @@
DEFAULT_FTYPE = 'auto'
DEFAULT_DBKEY = 'hg17'
-DEFAULT_INTERACTOR = "twill" # Default mechanism test code uses for interacting with Galaxy instance.
+DEFAULT_INTERACTOR = "api" # Default mechanism test code uses for interacting with Galaxy instance.
DEFAULT_MAX_SECS = 120
https://bitbucket.org/galaxy/galaxy-central/commits/92238b01c946/
Changeset: 92238b01c946
User: jmchilton
Date: 2014-09-05 21:19:18
Summary: Add another example tool - to illustrate three ways to collect files for concatenation.
Affected #: 3 files
diff -r 8b450e9886e1e8d62201bf307d79e2f8e3458fbd -r 92238b01c946c03b93f4b77d0729b24684940c89 test/functional/tools/for_workflows/cat_collection.xml
--- /dev/null
+++ b/test/functional/tools/for_workflows/cat_collection.xml
@@ -0,0 +1,16 @@
+<tool id="cat_collection" name="Concatenate dataset collection (for test workflows)">
+ <description>tail-to-head</description>
+ <command>
+ cat #for $q in $input1# $q #end for# > $out_file1
+ </command>
+ <inputs>
+ <param name="input1" type="data_collection" label="Concatenate Dataset" collection_type="paired" />
+ </inputs>
+ <outputs>
+ <data name="out_file1" format="input" />
+ </outputs>
+ <tests>
+ </tests>
+ <help>
+ </help>
+</tool>
diff -r 8b450e9886e1e8d62201bf307d79e2f8e3458fbd -r 92238b01c946c03b93f4b77d0729b24684940c89 test/functional/tools/for_workflows/cat_list.xml
--- a/test/functional/tools/for_workflows/cat_list.xml
+++ b/test/functional/tools/for_workflows/cat_list.xml
@@ -1,4 +1,4 @@
-<tool id="cat_list" name="Concatenate dataset list (for test workflows)">
+<tool id="cat_list" name="Concatenate multiple datasets (for test workflows)"><description>tail-to-head</description><command>
cat #for $q in $input1# $q #end for# > $out_file1
diff -r 8b450e9886e1e8d62201bf307d79e2f8e3458fbd -r 92238b01c946c03b93f4b77d0729b24684940c89 test/functional/tools/samples_tool_conf.xml
--- a/test/functional/tools/samples_tool_conf.xml
+++ b/test/functional/tools/samples_tool_conf.xml
@@ -28,8 +28,13 @@
<tool file="collection_optional_param.xml" /><!-- Tools interesting only for building up test workflows. -->
+
+ <!-- Next three tools demonstrate concatenating multiple datasets
+ with a repeat, multiple datasets with a multiple input data
+ parameter, and multiple datasets from a collection. --><tool file="for_workflows/cat.xml" /><tool file="for_workflows/cat_list.xml" />
+ <tool file="for_workflows/cat_collection.xml" /><tool file="for_workflows/head.xml" /></toolbox>
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: natefoo: Handle Pulsar job stop when setting metadata remotely.
by commits-noreply@bitbucket.org 05 Sep '14
by commits-noreply@bitbucket.org 05 Sep '14
05 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/77e633e92604/
Changeset: 77e633e92604
User: natefoo
Date: 2014-09-05 17:41:05
Summary: Handle Pulsar job stop when setting metadata remotely.
Affected #: 1 file
diff -r be2c86442b3e28407d7b845a90420c3feb3fe859 -r 77e633e926043524691dcc193f3e7d59809d06a8 lib/galaxy/jobs/runners/pulsar.py
--- a/lib/galaxy/jobs/runners/pulsar.py
+++ b/lib/galaxy/jobs/runners/pulsar.py
@@ -416,8 +416,9 @@
def stop_job( self, job ):
#if our local job has JobExternalOutputMetadata associated, then our primary job has to have already finished
+ client = self.get_client( job.destination_params, job.job_runner_external_id )
job_ext_output_metadata = job.get_external_output_metadata()
- if job_ext_output_metadata:
+ if not PulsarJobRunner.__remote_metadata( client ) and job_ext_output_metadata:
pid = job_ext_output_metadata[0].job_runner_external_pid # every JobExternalOutputMetadata has a pid set, we just need to take from one of them
if pid in [ None, '' ]:
log.warning( "stop_job(): %s: no PID in database for job, unable to stop" % job.id )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: guerler: ToolForm: Additional buttons, fixes and improvements, genome build selector, first commit for multiple dataset and collection selector
by commits-noreply@bitbucket.org 05 Sep '14
by commits-noreply@bitbucket.org 05 Sep '14
05 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/be2c86442b3e/
Changeset: be2c86442b3e
User: guerler
Date: 2014-09-05 17:24:55
Summary: ToolForm: Additional buttons, fixes and improvements, genome build selector, first commit for multiple dataset and collection selector
Affected #: 13 files
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 static/scripts/mvc/tools/tools-datasets.js
--- a/static/scripts/mvc/tools/tools-datasets.js
+++ b/static/scripts/mvc/tools/tools-datasets.js
@@ -6,7 +6,7 @@
this.currHistoryContents = new HISTORY_CONTENTS.HistoryContents({});
// identify current history id
- this.currHistoryContents.historyId = "f597429621d6eb2b";//Galaxy.currHistoryPanel.model.id;
+ this.currHistoryContents.historyId = options.history_id;
// make request
var self = this;
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 static/scripts/mvc/tools/tools-form.js
--- a/static/scripts/mvc/tools/tools-form.js
+++ b/static/scripts/mvc/tools/tools-form.js
@@ -40,7 +40,8 @@
// initialize datasets
this.datasets = new ToolDatasets({
- success: function() {
+ history_id : this.options.history_id,
+ success : function() {
self._initializeToolForm();
}
});
@@ -48,8 +49,40 @@
// initialize tool form
_initializeToolForm: function() {
+ // link this
+ var self = this;
+
+ // create question button
+ var button_question = new Ui.ButtonIcon({
+ icon : 'fa-question-circle',
+ title : 'Question?',
+ tooltip : 'Ask a question about this tool (Biostar)',
+ onclick : function() {
+ window.open(self.options.biostar_url + '/p/new/post/');
+ }
+ });
+
+ // create search button
+ var button_search = new Ui.ButtonIcon({
+ icon : 'fa-search',
+ title : 'Search',
+ tooltip : 'Search help for this tool (Biostar)',
+ onclick : function() {
+ window.open(self.options.biostar_url + '/t/' + self.options.id + '/');
+ }
+ });
+
+ // create share button
+ var button_share = new Ui.ButtonIcon({
+ icon : 'fa-share',
+ title : 'Share',
+ tooltip : 'Share this tool',
+ onclick : function() {
+ prompt('Copy to clipboard: Ctrl+C, Enter', galaxy_config.root + 'root?tool_id=' + self.options.id);
+ }
+ });
+
// fetch model and render form
- var self = this;
this.model.fetch({
error: function(response) {
console.debug('tools-form::_initializeToolForm() : Attempt to fetch tool model failed.');
@@ -72,9 +105,20 @@
console.log(self.tree.create(self));
}
})
+ },
+ operations: {
+ button_question: button_question,
+ button_search: button_search,
+ button_share: button_share
}
});
+ // configure button selection
+ if(!self.options.biostar_url) {
+ button_question.$el.hide();
+ button_search.$el.hide();
+ }
+
// create message
self.message = new Ui.Message();
self.portlet.append(self.message.$el);
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 static/scripts/mvc/tools/tools-section.js
--- a/static/scripts/mvc/tools/tools-section.js
+++ b/static/scripts/mvc/tools/tools-section.js
@@ -1,5 +1,5 @@
-define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/ui/ui-tabs'],
- function(Utils, Table, Ui, Tabs) {
+define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/ui/ui-tabs', 'mvc/tools/tools-select-dataset'],
+ function(Utils, Table, Ui, Tabs, SelectDataset) {
// create form view
var View = Backbone.View.extend({
@@ -202,7 +202,7 @@
// dataset column
case 'data_column':
- field = this._field_column(input_def);
+ field = this._field_data_colum(input_def);
break;
// conditional select field
@@ -229,11 +229,20 @@
case 'boolean':
field = this._field_boolean(input_def);
break;
-
- // default
- default:
+ }
+
+ // pick a generic field if specific mapping failed
+ if (!field) {
+ if (input_def.options) {
+ // assign select field
+ field = this._field_select(input_def);
+ } else {
+ // assign text field
field = this._field_text(input_def);
- console.debug('tools-form::_addRow() : Unmatched field type (' + field_type + ').');
+ }
+
+ // log
+ console.debug('tools-form::_addRow() : Auto matched field type (' + field_type + ').');
}
// set field value
@@ -319,28 +328,15 @@
// get element id
var id = input_def.id;
- // get datasets
- var datasets = this.app.datasets.filterType();
-
- // configure options fields
- var options = [];
- for (var i in datasets) {
- options.push({
- label: datasets[i].get('name'),
- value: datasets[i].get('id')
- });
- }
-
// select field
- return new Ui.Select.View({
+ return new SelectDataset.View(this.app, {
id : 'field-' + id,
- data : options,
- value : options[0].value,
+ extensions : input_def.extensions,
multiple : input_def.multiple,
onchange : function(value) {
// pick the first dataset if multiple might be selected
// TODO: iterate over all datasets and filter common/consistent columns
- if (input_def.multiple) {
+ if (value instanceof Array) {
value = value[0];
}
@@ -429,26 +425,22 @@
break;
}
- // force checkboxes if multiple has been selected
- if (input_def.multiple) {
- SelectClass = Ui.Checkbox;
- }
-
// select field
return new SelectClass.View({
id : 'field-' + input_def.id,
- data : options
+ data : options,
+ multiple: input_def.multiple
});
},
- // column selection field
- _field_column : function (input_def) {
+ // column field
+ _field_data_colum : function (input_def) {
return new Ui.Select.View({
id : 'field-' + input_def.id,
multiple: input_def.multiple
});
},
-
+
// text input field
_field_text : function(input_def) {
return new Ui.Input({
@@ -457,13 +449,20 @@
});
},
- // integer field
+ // slider field
_field_slider: function(input_def) {
+ // calculate step size
+ var step = 1;
+ if (input_def.type == 'float') {
+ step = (input_def.max - input_def.min) / 10000;
+ }
+
+ // create slider
return new Ui.Slider.View({
id : 'field-' + input_def.id,
min : input_def.min || 0,
max : input_def.max || 1000,
- decimal : input_def.type == 'float'
+ step : step
});
},
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 static/scripts/mvc/tools/tools-select-dataset.js
--- /dev/null
+++ b/static/scripts/mvc/tools/tools-select-dataset.js
@@ -0,0 +1,109 @@
+// dependencies
+define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-tabs'], function(Utils, Ui, Tabs) {
+
+var View = Backbone.View.extend({
+ // initialize
+ initialize : function(app, options) {
+ // link this
+ var self = this;
+
+ // get datasets
+ var datasets = app.datasets.filterType();
+
+ // configure options fields
+ var select_data = [];
+ for (var i in datasets) {
+ select_data.push({
+ label: datasets[i].get('name'),
+ value: datasets[i].get('id')
+ });
+ }
+
+ // create select field
+ this.select = new Ui.Select.View({
+ data : select_data,
+ value : select_data[0].value,
+ onchange : function() {
+ self.trigger('change');
+ }
+ });
+
+ // create select field for multiple files
+ this.select_multiple = new Ui.Select.View({
+ multiple : true,
+ data : select_data,
+ value : select_data[0].value,
+ onchange : function() {
+ self.trigger('change');
+ }
+ });
+
+
+ // create select field for multiple files
+ this.select_collection = new Ui.Select.View({
+ data : select_data,
+ value : select_data[0].value,
+ onchange : function() {
+ self.trigger('change');
+ }
+ });
+
+ // add change event. fires on trigger
+ this.on('change', function() {
+ if (options.onchange) {
+ options.onchange(self.value());
+ }
+ });
+
+ // tabs
+ this.tabs = new Tabs.View();
+
+ // add tab
+ this.tabs.add({
+ id : 'single',
+ title : 'Select a dataset',
+ $el : this.select.$el
+ });
+
+ // add tab
+ this.tabs.add({
+ id : 'multiple',
+ title : 'Select multiple datasets',
+ $el : this.select_multiple.$el
+ });
+
+ // add tab
+ this.tabs.add({
+ id : 'collection',
+ title : 'Select a dataset collection',
+ $el : this.select_collection.$el
+ });
+
+ // add element
+ this.setElement(this.tabs.$el);
+ },
+
+ // value
+ value : function (new_value) {
+ var current_tab = this.tabs.current();
+ switch(current_tab) {
+ case 'multiple' :
+ return this.select_multiple.value();
+ case 'collection' :
+ return this.select_collection.value();
+ default :
+ return this.select.value();
+ }
+ },
+
+ // render
+ update: function(options) {
+ this.select.update(options);
+ }
+});
+
+return {
+ View: View
+}
+
+});
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 static/scripts/mvc/ui/ui-slider.js
--- a/static/scripts/mvc/ui/ui-slider.js
+++ b/static/scripts/mvc/ui/ui-slider.js
@@ -8,8 +8,7 @@
value : '',
min : 1,
max : 100,
- step : 0.1,
- decimal : false
+ step : 1
},
// initialize
@@ -29,11 +28,6 @@
// backup integer field
this.$text = this.$el.find('#text');
- // set step size
- if (!this.options.decimal) {
- this.options.step = 1;
- }
-
// load slider plugin
this.$slider.slider(this.options);
@@ -46,7 +40,7 @@
this.$text.on('keydown', function (event) {
var v = event.which;
if (!(v == 13 || v == 8 || v == 37 || v == 39 || v == 189 || (v >= 48 && v <= 57)
- || (self.options.decimal && $(this).val().indexOf('.') == -1) && v == 190)) {
+ || (self.options.step != 1 && $(this).val().indexOf('.') == -1) && v == 190)) {
event.preventDefault();
}
});
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 static/scripts/mvc/ui/ui-tabs.js
--- a/static/scripts/mvc/ui/ui-tabs.js
+++ b/static/scripts/mvc/ui/ui-tabs.js
@@ -71,6 +71,11 @@
return _.size(this.list);
},
+ // front
+ current: function() {
+ return this.$el.find('.tab-pane.active').attr('id');
+ },
+
// append
add: function(options) {
// self
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 static/scripts/packed/mvc/tools/tools-datasets.js
--- a/static/scripts/packed/mvc/tools/tools-datasets.js
+++ b/static/scripts/packed/mvc/tools/tools-datasets.js
@@ -1,1 +1,1 @@
-define(["mvc/history/history-contents"],function(a){return Backbone.Model.extend({initialize:function(c){this.currHistoryContents=new a.HistoryContents({});this.currHistoryContents.historyId="f597429621d6eb2b";var b=this;var d=this.currHistoryContents.fetchAllDetails().done(function(){console.debug("tools-datasets::initialize() - Completed.");c.success&&c.success()}).fail(function(){console.debug("tools-datasets::initialize() - Ajax request failed.")})},filterType:function(b){return this.currHistoryContents.filter(function(c){var d=c.get("history_content_type");var e=c.get("file_ext");return d==="dataset"})},filter:function(b){return _.first(this.currHistoryContents.filter(function(c){return c.get("id")===b}))}})});
\ No newline at end of file
+define(["mvc/history/history-contents"],function(a){return Backbone.Model.extend({initialize:function(c){this.currHistoryContents=new a.HistoryContents({});this.currHistoryContents.historyId=c.history_id;var b=this;var d=this.currHistoryContents.fetchAllDetails().done(function(){console.debug("tools-datasets::initialize() - Completed.");c.success&&c.success()}).fail(function(){console.debug("tools-datasets::initialize() - Ajax request failed.")})},filterType:function(b){return this.currHistoryContents.filter(function(c){var d=c.get("history_content_type");var e=c.get("file_ext");return d==="dataset"})},filter:function(b){return _.first(this.currHistoryContents.filter(function(c){return c.get("id")===b}))}})});
\ No newline at end of file
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 static/scripts/packed/mvc/tools/tools-form.js
--- a/static/scripts/packed/mvc/tools/tools-form.js
+++ b/static/scripts/packed/mvc/tools/tools-form.js
@@ -1,1 +1,1 @@
-define(["mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/citation/citation-model","mvc/citation/citation-view","mvc/tools","mvc/tools/tools-template","mvc/tools/tools-datasets","mvc/tools/tools-section","mvc/tools/tools-tree"],function(g,k,i,a,f,d,h,j,c){var e=Backbone.Model.extend({initialize:function(l){this.url=galaxy_config.root+"api/tools/"+l.id+"?io_details=true"}});var b=Backbone.View.extend({main_el:"body",initialize:function(m){var l=this;this.options=m;this.model=new e({id:m.id});this.tree=new c(this);this.field_list={};this.input_list={};this.datasets=new h({success:function(){l._initializeToolForm()}})},_initializeToolForm:function(){var l=this;this.model.fetch({error:function(m){console.debug("tools-form::_initializeToolForm() : Attempt to fetch tool model failed.")},success:function(){l.inputs=l.model.get("inputs");l.portlet=new g.View({icon:"fa-wrench",title:"<b>"+l.model.get("name")+"</b> "+l.model.get("description"),buttons:{execute:new k.ButtonIcon({icon:"fa-check",tooltip:"Execute the tool",title:"Execute",floating:"clear",onclick:function(){console.log(l.tree.create(l))}})}});l.message=new k.Message();l.portlet.append(l.message.$el);$(l.main_el).append(l.portlet.$el);if(l.options.help!=""){$(l.main_el).append(d.help(l.options.help))}if(l.options.citations){$(l.main_el).append(d.citations());var m=new i.ToolCitationCollection();m.tool_id=l.options.id;var n=new a.CitationListView({collection:m});n.render();m.fetch()}l.setElement(l.portlet.content());l.section=new j.View(l,{inputs:l.model.get("inputs")});l.portlet.append(l.section.$el);l.refresh()}})},refresh:function(){this.tree.refresh();for(var l in this.field_list){this.field_list[l].trigger("change")}console.debug("tools-form::refresh() - Recreated tree structure. Refresh.")}});return{View:b}});
\ No newline at end of file
+define(["mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/citation/citation-model","mvc/citation/citation-view","mvc/tools","mvc/tools/tools-template","mvc/tools/tools-datasets","mvc/tools/tools-section","mvc/tools/tools-tree"],function(g,k,i,a,f,d,h,j,c){var e=Backbone.Model.extend({initialize:function(l){this.url=galaxy_config.root+"api/tools/"+l.id+"?io_details=true"}});var b=Backbone.View.extend({main_el:"body",initialize:function(m){var l=this;this.options=m;this.model=new e({id:m.id});this.tree=new c(this);this.field_list={};this.input_list={};this.datasets=new h({history_id:this.options.history_id,success:function(){l._initializeToolForm()}})},_initializeToolForm:function(){var m=this;var n=new k.ButtonIcon({icon:"fa-question-circle",title:"Question?",tooltip:"Ask a question about this tool (Biostar)",onclick:function(){window.open(m.options.biostar_url+"/p/new/post/")}});var o=new k.ButtonIcon({icon:"fa-search",title:"Search",tooltip:"Search help for this tool (Biostar)",onclick:function(){window.open(m.options.biostar_url+"/t/"+m.options.id+"/")}});var l=new k.ButtonIcon({icon:"fa-share",title:"Share",tooltip:"Share this tool",onclick:function(){prompt("Copy to clipboard: Ctrl+C, Enter",galaxy_config.root+"root?tool_id="+m.options.id)}});this.model.fetch({error:function(p){console.debug("tools-form::_initializeToolForm() : Attempt to fetch tool model failed.")},success:function(){m.inputs=m.model.get("inputs");m.portlet=new g.View({icon:"fa-wrench",title:"<b>"+m.model.get("name")+"</b> "+m.model.get("description"),buttons:{execute:new k.ButtonIcon({icon:"fa-check",tooltip:"Execute the tool",title:"Execute",floating:"clear",onclick:function(){console.log(m.tree.create(m))}})},operations:{button_question:n,button_search:o,button_share:l}});if(!m.options.biostar_url){n.$el.hide();o.$el.hide()}m.message=new k.Message();m.portlet.append(m.message.$el);$(m.main_el).append(m.portlet.$el);if(m.options.help!=""){$(m.main_el).append(d.help(m.options.help))}if(m.options.citations){$(m.main_el).append(d.citations());var p=new i.ToolCitationCollection();p.tool_id=m.options.id;var q=new a.CitationListView({collection:p});q.render();p.fetch()}m.setElement(m.portlet.content());m.section=new j.View(m,{inputs:m.model.get("inputs")});m.portlet.append(m.section.$el);m.refresh()}})},refresh:function(){this.tree.refresh();for(var l in this.field_list){this.field_list[l].trigger("change")}console.debug("tools-form::refresh() - Recreated tree structure. Refresh.")}});return{View:b}});
\ No newline at end of file
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 static/scripts/packed/mvc/tools/tools-section.js
--- a/static/scripts/packed/mvc/tools/tools-section.js
+++ b/static/scripts/packed/mvc/tools/tools-section.js
@@ -1,1 +1,1 @@
-define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-tabs"],function(c,b,e,a){var d=Backbone.View.extend({initialize:function(g,f){this.app=g;this.inputs=f.inputs;f.cls_tr="section-row";this.table=new b.View(f);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var f in this.inputs){this._add(this.inputs[f])}},_add:function(h){var g=this;var f=jQuery.extend(true,{},h);f.id=c.uuid();this.app.input_list[f.id]=f;var i=f.type;switch(i){case"conditional":this._addConditional(f);break;case"repeat":this._addRepeat(f);break;default:this._addRow(i,f)}},_addConditional:function(f){f.label=f.test_param.label;f.value=f.test_param.value;this._addRow("conditional",f);for(var h in f.cases){var g=f.id+"-section-"+h;var j=new d(this.app,{inputs:f.cases[h].inputs,cls:"ui-table-plain"});this.table.add("");this.table.add(j.$el);this.table.append(g)}},_addRepeat:function(f){var g=this;var k=new a.View({title_new:"Add "+f.title,max:f.max,onnew:function(){var i=f.id+"-section-"+c.uuid();var m=new d(g.app,{inputs:f.inputs,cls:"ui-table-plain"});k.add({id:i,title:f.title,$el:m.$el,ondel:function(){k.del(i);k.retitle(f.title);g.app.refresh()}});k.retitle(f.title);k.show(i);g.app.refresh()}});for(var j=0;j<f.min;j++){var h=f.id+"-section-"+c.uuid();var l=new d(g.app,{inputs:f.inputs,cls:"ui-table-plain"});k.add({id:h,title:f.title,$el:l.$el})}k.retitle(f.title);this.table.add("");this.table.add(k.$el);this.table.append(f.id)},_addRow:function(h,f){var j=f.id;var g=null;switch(h){case"text":g=this._field_text(f);break;case"select":g=this._field_select(f);break;case"data":g=this._field_data(f);break;case"data_column":g=this._field_column(f);break;case"conditional":g=this._field_conditional(f);break;case"hidden":g=this._field_hidden(f);break;case"integer":g=this._field_slider(f);break;case"float":g=this._field_slider(f);break;case"boolean":g=this._field_boolean(f);break;default:g=this._field_text(f);console.debug("tools-form::_addRow() : Unmatched field type ("+h+").")}if(f.value!==undefined){g.value(f.value)}this.app.field_list[j]=g;var i=$("<div/>");i.append(g.$el);if(f.help){i.append('<div class="ui-table-form-info">'+f.help+"</div>")}this.table.add('<span class="ui-table-form-title">'+f.label+"</span>","20%");this.table.add(i);this.table.append(j)},_field_conditional:function(f){var g=this;var h=[];for(var j in f.test_param.options){var k=f.test_param.options[j];h.push({label:k[0],value:k[1]})}return new e.Select.View({id:"field-"+f.id,data:h,onchange:function(s){for(var q in f.cases){var m=f.cases[q];var p=f.id+"-section-"+q;var l=g.table.get(p);var o=false;for(var n in m.inputs){var r=m.inputs[n].type;if(r&&r!=="hidden"){o=true;break}}if(m.value==s&&o){l.fadeIn("fast")}else{l.hide()}}}})},_field_data:function(f){var g=this;var l=f.id;var k=this.app.datasets.filterType();var h=[];for(var j in k){h.push({label:k[j].get("name"),value:k[j].get("id")})}return new e.Select.View({id:"field-"+l,data:h,value:h[0].value,onchange:function(u){var s=g.app.tree.findReferences(l,"data_column");var n=g.app.datasets.filter(u);if(n&&s.length>0){console.debug("tool-form::field_data() - Selected dataset "+u+".");var w=n.get("metadata_column_types");if(!w){console.debug("tool-form::field_data() - FAILED: Could not find metadata for dataset "+u+".")}for(var p in s){var q=g.app.input_list[s[p]];var r=g.app.field_list[s[p]];if(!q||!r){console.debug("tool-form::field_data() - FAILED: Column not found.")}var o=q.numerical;var m=[];for(var v in w){var t=w[v];if(t=="int"||t=="float"||!o){m.push({label:"Column: "+(parseInt(v)+1)+" ["+w[v]+"]",value:v})}}if(r){r.update(m);if(!r.exists(r.value())){r.value(r.first())}}}}else{console.debug("tool-form::field_data() - FAILED: Could not find dataset "+u+".")}}})},_field_select:function(f){var g=[];for(var h in f.options){var j=f.options[h];g.push({label:j[0],value:j[1]})}var k=e.Select;switch(f.display){case"checkboxes":k=e.Checkbox;break;case"radio":k=e.RadioButton;break}if(f.multiple){k=e.Checkbox}return new k.View({id:"field-"+f.id,data:g})},_field_column:function(f){return new e.Select.View({id:"field-"+f.id,multiple:f.multiple})},_field_text:function(f){return new e.Input({id:"field-"+f.id,area:f.area})},_field_slider:function(f){return new e.Slider.View({id:"field-"+f.id,min:f.min||0,max:f.max||1000,decimal:f.type=="float"})},_field_hidden:function(f){return new e.Hidden({id:"field-"+f.id})},_field_boolean:function(f){return new e.RadioButton.View({id:"field-"+f.id,data:[{label:"Yes",value:true},{label:"No",value:false}]})}});return{View:d}});
\ No newline at end of file
+define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-tabs","mvc/tools/tools-select-dataset"],function(d,b,f,a,c){var e=Backbone.View.extend({initialize:function(h,g){this.app=h;this.inputs=g.inputs;g.cls_tr="section-row";this.table=new b.View(g);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var g in this.inputs){this._add(this.inputs[g])}},_add:function(i){var h=this;var g=jQuery.extend(true,{},i);g.id=d.uuid();this.app.input_list[g.id]=g;var j=g.type;switch(j){case"conditional":this._addConditional(g);break;case"repeat":this._addRepeat(g);break;default:this._addRow(j,g)}},_addConditional:function(g){g.label=g.test_param.label;g.value=g.test_param.value;this._addRow("conditional",g);for(var j in g.cases){var h=g.id+"-section-"+j;var k=new e(this.app,{inputs:g.cases[j].inputs,cls:"ui-table-plain"});this.table.add("");this.table.add(k.$el);this.table.append(h)}},_addRepeat:function(g){var h=this;var l=new a.View({title_new:"Add "+g.title,max:g.max,onnew:function(){var i=g.id+"-section-"+d.uuid();var n=new e(h.app,{inputs:g.inputs,cls:"ui-table-plain"});l.add({id:i,title:g.title,$el:n.$el,ondel:function(){l.del(i);l.retitle(g.title);h.app.refresh()}});l.retitle(g.title);l.show(i);h.app.refresh()}});for(var k=0;k<g.min;k++){var j=g.id+"-section-"+d.uuid();var m=new e(h.app,{inputs:g.inputs,cls:"ui-table-plain"});l.add({id:j,title:g.title,$el:m.$el})}l.retitle(g.title);this.table.add("");this.table.add(l.$el);this.table.append(g.id)},_addRow:function(i,g){var k=g.id;var h=null;switch(i){case"text":h=this._field_text(g);break;case"select":h=this._field_select(g);break;case"data":h=this._field_data(g);break;case"data_column":h=this._field_data_colum(g);break;case"conditional":h=this._field_conditional(g);break;case"hidden":h=this._field_hidden(g);break;case"integer":h=this._field_slider(g);break;case"float":h=this._field_slider(g);break;case"boolean":h=this._field_boolean(g);break}if(!h){if(g.options){h=this._field_select(g)}else{h=this._field_text(g)}console.debug("tools-form::_addRow() : Auto matched field type ("+i+").")}if(g.value!==undefined){h.value(g.value)}this.app.field_list[k]=h;var j=$("<div/>");j.append(h.$el);if(g.help){j.append('<div class="ui-table-form-info">'+g.help+"</div>")}this.table.add('<span class="ui-table-form-title">'+g.label+"</span>","20%");this.table.add(j);this.table.append(k)},_field_conditional:function(g){var h=this;var j=[];for(var k in g.test_param.options){var l=g.test_param.options[k];j.push({label:l[0],value:l[1]})}return new f.Select.View({id:"field-"+g.id,data:j,onchange:function(t){for(var r in g.cases){var n=g.cases[r];var q=g.id+"-section-"+r;var m=h.table.get(q);var p=false;for(var o in n.inputs){var s=n.inputs[o].type;if(s&&s!=="hidden"){p=true;break}}if(n.value==t&&p){m.fadeIn("fast")}else{m.hide()}}}})},_field_data:function(g){var h=this;var i=g.id;return new c.View(this.app,{id:"field-"+i,extensions:g.extensions,multiple:g.multiple,onchange:function(r){if(r instanceof Array){r=r[0]}var p=h.app.tree.findReferences(i,"data_column");var k=h.app.datasets.filter(r);if(k&&p.length>0){console.debug("tool-form::field_data() - Selected dataset "+r+".");var t=k.get("metadata_column_types");if(!t){console.debug("tool-form::field_data() - FAILED: Could not find metadata for dataset "+r+".")}for(var m in p){var n=h.app.input_list[p[m]];var o=h.app.field_list[p[m]];if(!n||!o){console.debug("tool-form::field_data() - FAILED: Column not found.")}var l=n.numerical;var j=[];for(var s in t){var q=t[s];if(q=="int"||q=="float"||!l){j.push({label:"Column: "+(parseInt(s)+1)+" ["+t[s]+"]",value:s})}}if(o){o.update(j);if(!o.exists(o.value())){o.value(o.first())}}}}else{console.debug("tool-form::field_data() - FAILED: Could not find dataset "+r+".")}}})},_field_select:function(g){var h=[];for(var j in g.options){var k=g.options[j];h.push({label:k[0],value:k[1]})}var l=f.Select;switch(g.display){case"checkboxes":l=f.Checkbox;break;case"radio":l=f.RadioButton;break}return new l.View({id:"field-"+g.id,data:h,multiple:g.multiple})},_field_data_colum:function(g){return new f.Select.View({id:"field-"+g.id,multiple:g.multiple})},_field_text:function(g){return new f.Input({id:"field-"+g.id,area:g.area})},_field_slider:function(g){var h=1;if(g.type=="float"){h=(g.max-g.min)/10000}return new f.Slider.View({id:"field-"+g.id,min:g.min||0,max:g.max||1000,step:h})},_field_hidden:function(g){return new f.Hidden({id:"field-"+g.id})},_field_boolean:function(g){return new f.RadioButton.View({id:"field-"+g.id,data:[{label:"Yes",value:true},{label:"No",value:false}]})}});return{View:e}});
\ No newline at end of file
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 static/scripts/packed/mvc/tools/tools-select-dataset.js
--- /dev/null
+++ b/static/scripts/packed/mvc/tools/tools-select-dataset.js
@@ -0,0 +1,1 @@
+define(["utils/utils","mvc/ui/ui-misc","mvc/ui/ui-tabs"],function(b,d,a){var c=Backbone.View.extend({initialize:function(k,f){var e=this;var h=k.datasets.filterType();var j=[];for(var g in h){j.push({label:h[g].get("name"),value:h[g].get("id")})}this.select=new d.Select.View({data:j,value:j[0].value,onchange:function(){e.trigger("change")}});this.select_multiple=new d.Select.View({multiple:true,data:j,value:j[0].value,onchange:function(){e.trigger("change")}});this.select_collection=new d.Select.View({data:j,value:j[0].value,onchange:function(){e.trigger("change")}});this.on("change",function(){if(f.onchange){f.onchange(e.value())}});this.tabs=new a.View();this.tabs.add({id:"single",title:"Select a dataset",$el:this.select.$el});this.tabs.add({id:"multiple",title:"Select multiple datasets",$el:this.select_multiple.$el});this.tabs.add({id:"collection",title:"Select a dataset collection",$el:this.select_collection.$el});this.setElement(this.tabs.$el)},value:function(e){var f=this.tabs.current();switch(f){case"multiple":return this.select_multiple.value();case"collection":return this.select_collection.value();default:return this.select.value()}},update:function(e){this.select.update(e)}});return{View:c}});
\ No newline at end of file
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 static/scripts/packed/mvc/ui/ui-slider.js
--- a/static/scripts/packed/mvc/ui/ui-slider.js
+++ b/static/scripts/packed/mvc/ui/ui-slider.js
@@ -1,1 +1,1 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{value:"",min:1,max:100,step:0.1,decimal:false},initialize:function(d){var c=this;this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.$slider=this.$el.find("#slider");this.$text=this.$el.find("#text");if(!this.options.decimal){this.options.step=1}this.$slider.slider(this.options);this.$text.on("change",function(){c.value($(this).val())});this.$text.on("keydown",function(f){var e=f.which;if(!(e==13||e==8||e==37||e==39||e==189||(e>=48&&e<=57)||(c.options.decimal&&$(this).val().indexOf(".")==-1)&&e==190)){f.preventDefault()}});this.$slider.on("slide",function(e,f){c.value(f.value)})},value:function(c){if(c!==undefined){c=Math.max(Math.min(c,this.options.max),this.options.min);if(this.options.onchange){this.options.onchange(c)}this.$slider.slider("value",c);this.$text.val(c)}return this.$text.val()},_template:function(c){return'<div id="'+c.id+'" style=""><input id="text" type="text" class="ui-input" style="width: 50px; float: left"/><div id="slider" style="width: calc(100% - 60px); float: left; top: 8px; left: 10px;"/></div>'}});return{View:b}});
\ No newline at end of file
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{value:"",min:1,max:100,step:1},initialize:function(d){var c=this;this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.$slider=this.$el.find("#slider");this.$text=this.$el.find("#text");this.$slider.slider(this.options);this.$text.on("change",function(){c.value($(this).val())});this.$text.on("keydown",function(f){var e=f.which;if(!(e==13||e==8||e==37||e==39||e==189||(e>=48&&e<=57)||(c.options.step!=1&&$(this).val().indexOf(".")==-1)&&e==190)){f.preventDefault()}});this.$slider.on("slide",function(e,f){c.value(f.value)})},value:function(c){if(c!==undefined){c=Math.max(Math.min(c,this.options.max),this.options.min);if(this.options.onchange){this.options.onchange(c)}this.$slider.slider("value",c);this.$text.val(c)}return this.$text.val()},_template:function(c){return'<div id="'+c.id+'" style=""><input id="text" type="text" class="ui-input" style="width: 50px; float: left"/><div id="slider" style="width: calc(100% - 60px); float: left; top: 8px; left: 10px;"/></div>'}});return{View:b}});
\ No newline at end of file
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 static/scripts/packed/mvc/ui/ui-tabs.js
--- a/static/scripts/packed/mvc/ui/ui-tabs.js
+++ b/static/scripts/packed/mvc/ui/ui-tabs.js
@@ -1,1 +1,1 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{title_new:"",operations:null,onnew:null,min:null,max:null},initialize:function(e){this.visible=false;this.$nav=null;this.$content=null;this.first_tab=null;this.options=a.merge(e,this.optionsDefault);var c=$(this._template(this.options));this.$nav=c.find(".tab-navigation");this.$content=c.find(".tab-content");this.setElement(c);this.list={};var d=this;if(this.options.operations){$.each(this.options.operations,function(g,h){h.$el.prop("id",g);d.$nav.find(".operations").append(h.$el)})}if(this.options.onnew){var f=$(this._template_tab_new(this.options));this.$nav.append(f);f.tooltip({title:"Add a new tab",placement:"bottom",container:d.$el});f.on("click",function(g){f.tooltip("hide");d.options.onnew()})}},size:function(){return _.size(this.list)},add:function(f){var e=this;var h=f.id;var g=$(this._template_tab(f));var d=$(this._template_tab_content(f));this.list[h]=f.ondel?true:false;if(this.options.onnew){this.$nav.find("#new-tab").before(g)}else{this.$nav.append(g)}d.append(f.$el);this.$content.append(d);if(this.size()==1){g.addClass("active");d.addClass("active");this.first_tab=h}if(this.options.max&&this.size()>=this.options.max){this.$el.find("#new-tab").hide()}if(f.ondel){var c=g.find("#delete");c.tooltip({title:"Delete this tab",placement:"bottom",container:e.$el});c.on("click",function(){c.tooltip("destroy");e.$el.find(".tooltip").remove();f.ondel();return false})}g.on("click",function(i){i.preventDefault();if(f.onclick){f.onclick()}else{e.show(h)}})},del:function(c){this.$el.find("#tab-"+c).remove();this.$el.find("#"+c).remove();if(this.first_tab==c){this.first_tab=null}if(this.first_tab!=null){this.show(this.first_tab)}if(this.list[c]){delete this.list[c]}if(this.size()<this.options.max){this.$el.find("#new-tab").show()}},delRemovable:function(){for(var c in this.list){this.del(c)}},show:function(c){this.$el.fadeIn("fast");this.visible=true;if(c){this.$el.find(".tab-element").removeClass("active");this.$el.find(".tab-pane").removeClass("active");this.$el.find("#tab-"+c).addClass("active");this.$el.find("#"+c).addClass("active")}},hide:function(){this.$el.fadeOut("fast");this.visible=false},hideOperation:function(c){this.$nav.find("#"+c).hide()},showOperation:function(c){this.$nav.find("#"+c).show()},setOperation:function(e,d){var c=this.$nav.find("#"+e);c.off("click");c.on("click",d)},title:function(e,d){var c=this.$el.find("#tab-title-text-"+e);if(d){c.html(d)}return c.html()},retitle:function(d){var c=0;for(var e in this.list){this.title(e,++c+": "+d)}},_template:function(c){return'<div class="ui-tabs tabbable tabs-left"><ul id="tab-navigation" class="tab-navigation nav nav-tabs"><div class="operations" style="float: right; margin-bottom: 4px;"></div></ul><div id="tab-content" class="tab-content"/></div>'},_template_tab_new:function(c){return'<li id="new-tab"><a href="javascript:void(0);"><i class="ui-tabs-add fa fa-plus-circle"/>'+c.title_new+"</a></li>"},_template_tab:function(d){var c='<li id="tab-'+d.id+'" class="tab-element"><a id="tab-title-link-'+d.id+'" title="" href="#'+d.id+'" data-original-title=""><span id="tab-title-text-'+d.id+'" class="tab-title-text">'+d.title+"</span>";if(d.ondel){c+='<i id="delete" class="ui-tabs-delete fa fa-minus-circle"/>'}c+="</a></li>";return c},_template_tab_content:function(c){return'<div id="'+c.id+'" class="tab-pane"/>'}});return{View:b}});
\ No newline at end of file
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{title_new:"",operations:null,onnew:null,min:null,max:null},initialize:function(e){this.visible=false;this.$nav=null;this.$content=null;this.first_tab=null;this.options=a.merge(e,this.optionsDefault);var c=$(this._template(this.options));this.$nav=c.find(".tab-navigation");this.$content=c.find(".tab-content");this.setElement(c);this.list={};var d=this;if(this.options.operations){$.each(this.options.operations,function(g,h){h.$el.prop("id",g);d.$nav.find(".operations").append(h.$el)})}if(this.options.onnew){var f=$(this._template_tab_new(this.options));this.$nav.append(f);f.tooltip({title:"Add a new tab",placement:"bottom",container:d.$el});f.on("click",function(g){f.tooltip("hide");d.options.onnew()})}},size:function(){return _.size(this.list)},current:function(){return this.$el.find(".tab-pane.active").attr("id")},add:function(f){var e=this;var h=f.id;var g=$(this._template_tab(f));var d=$(this._template_tab_content(f));this.list[h]=f.ondel?true:false;if(this.options.onnew){this.$nav.find("#new-tab").before(g)}else{this.$nav.append(g)}d.append(f.$el);this.$content.append(d);if(this.size()==1){g.addClass("active");d.addClass("active");this.first_tab=h}if(this.options.max&&this.size()>=this.options.max){this.$el.find("#new-tab").hide()}if(f.ondel){var c=g.find("#delete");c.tooltip({title:"Delete this tab",placement:"bottom",container:e.$el});c.on("click",function(){c.tooltip("destroy");e.$el.find(".tooltip").remove();f.ondel();return false})}g.on("click",function(i){i.preventDefault();if(f.onclick){f.onclick()}else{e.show(h)}})},del:function(c){this.$el.find("#tab-"+c).remove();this.$el.find("#"+c).remove();if(this.first_tab==c){this.first_tab=null}if(this.first_tab!=null){this.show(this.first_tab)}if(this.list[c]){delete this.list[c]}if(this.size()<this.options.max){this.$el.find("#new-tab").show()}},delRemovable:function(){for(var c in this.list){this.del(c)}},show:function(c){this.$el.fadeIn("fast");this.visible=true;if(c){this.$el.find(".tab-element").removeClass("active");this.$el.find(".tab-pane").removeClass("active");this.$el.find("#tab-"+c).addClass("active");this.$el.find("#"+c).addClass("active")}},hide:function(){this.$el.fadeOut("fast");this.visible=false},hideOperation:function(c){this.$nav.find("#"+c).hide()},showOperation:function(c){this.$nav.find("#"+c).show()},setOperation:function(e,d){var c=this.$nav.find("#"+e);c.off("click");c.on("click",d)},title:function(e,d){var c=this.$el.find("#tab-title-text-"+e);if(d){c.html(d)}return c.html()},retitle:function(d){var c=0;for(var e in this.list){this.title(e,++c+": "+d)}},_template:function(c){return'<div class="ui-tabs tabbable tabs-left"><ul id="tab-navigation" class="tab-navigation nav nav-tabs"><div class="operations" style="float: right; margin-bottom: 4px;"></div></ul><div id="tab-content" class="tab-content"/></div>'},_template_tab_new:function(c){return'<li id="new-tab"><a href="javascript:void(0);"><i class="ui-tabs-add fa fa-plus-circle"/>'+c.title_new+"</a></li>"},_template_tab:function(d){var c='<li id="tab-'+d.id+'" class="tab-element"><a id="tab-title-link-'+d.id+'" title="" href="#'+d.id+'" data-original-title=""><span id="tab-title-text-'+d.id+'" class="tab-title-text">'+d.title+"</span>";if(d.ondel){c+='<i id="delete" class="ui-tabs-delete fa fa-minus-circle"/>'}c+="</a></li>";return c},_template_tab_content:function(c){return'<div id="'+c.id+'" class="tab-pane"/>'}});return{View:b}});
\ No newline at end of file
diff -r 0df64c45db90ca84e763c34919ebe9304d96f202 -r be2c86442b3e28407d7b845a90420c3feb3fe859 templates/webapps/galaxy/tool_form.api.mako
--- a/templates/webapps/galaxy/tool_form.api.mako
+++ b/templates/webapps/galaxy/tool_form.api.mako
@@ -24,9 +24,11 @@
# form configuration
self.form_config = {
- 'id' : tool.id,
- 'help' : tool_help,
- 'citations' : tool_citations
+ 'id' : tool.id,
+ 'help' : tool_help,
+ 'citations' : tool_citations,
+ 'biostar_url' : trans.app.config.biostar_url,
+ 'history_id' : trans.security.encode_id( trans.history.id )
}
%>
${h.js( "libs/bibtex", "libs/jquery/jquery-ui" )}
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/d7c19ee54a91/
Changeset: d7c19ee54a91
User: jmchilton
Date: 2014-09-05 02:34:52
Summary: More fixes for library permissions backward compatiblity.
Fixes failing test in test/api/test_history_contents.py.
Affected #: 2 files
diff -r 6a718b389bf1caa5461c5ff7a7dd644f1279cd69 -r d7c19ee54a9167b97f99eb83c2600f35abdea312 lib/galaxy/webapps/galaxy/api/libraries.py
--- a/lib/galaxy/webapps/galaxy/api/libraries.py
+++ b/lib/galaxy/webapps/galaxy/api/libraries.py
@@ -284,6 +284,7 @@
action = kwd.get( 'action', None )
if action is None:
payload = kwd.get( 'payload', None )
+ del kwd[ 'payload' ]
if payload is not None:
return self.set_permissions_old( trans, library, payload, **kwd )
else:
@@ -368,6 +369,7 @@
POST /api/libraries/{encoded_library_id}/permissions
Updates the library permissions.
"""
+ import galaxy.util
params = galaxy.util.Params( payload )
permissions = {}
for k, v in trans.app.model.Library.permitted_actions.items():
diff -r 6a718b389bf1caa5461c5ff7a7dd644f1279cd69 -r d7c19ee54a9167b97f99eb83c2600f35abdea312 lib/galaxy/webapps/galaxy/buildapp.py
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -299,11 +299,6 @@
path_prefix='/api/libraries/:library_id',
parent_resources=dict( member_name='library', collection_name='libraries' ) )
- webapp.mapper.resource( 'permission',
- 'permissions',
- path_prefix='/api/libraries/:library_id',
- parent_resources=dict( member_name='library', collection_name='libraries' ) )
-
_add_item_extended_metadata_controller( webapp,
name_prefix="library_dataset_",
path_prefix='/api/libraries/:library_id/contents/:library_content_id' )
https://bitbucket.org/galaxy/galaxy-central/commits/0df64c45db90/
Changeset: 0df64c45db90
User: jmchilton
Date: 2014-09-05 02:34:52
Summary: Remove abstraction around requests now that it is a required dependency.
Affected #: 7 files
diff -r d7c19ee54a9167b97f99eb83c2600f35abdea312 -r 0df64c45db90ca84e763c34919ebe9304d96f202 test/api/test_authenticate.py
--- a/test/api/test_authenticate.py
+++ b/test/api/test_authenticate.py
@@ -1,6 +1,6 @@
import base64
-from base.interactor import get_request
+from requests import get
from base import api
@@ -18,12 +18,12 @@
headers = {
"Authorization": authorization,
}
- auth_response = get_request( baseauth_url, headers=headers )
+ auth_response = get( baseauth_url, headers=headers )
self._assert_status_code_is( auth_response, 200 )
auth_dict = auth_response.json()
self._assert_has_keys( auth_dict, "api_key" )
# Verify key...
random_api_url = self._api_url( "users", use_key=False )
- random_api_response = get_request( random_api_url, params=dict( key=auth_dict[ "api_key" ] ) )
+ random_api_response = get( random_api_url, params=dict( key=auth_dict[ "api_key" ] ) )
self._assert_status_code_is( random_api_response, 200 )
diff -r d7c19ee54a9167b97f99eb83c2600f35abdea312 -r 0df64c45db90ca84e763c34919ebe9304d96f202 test/api/test_histories.py
--- a/test/api/test_histories.py
+++ b/test/api/test_histories.py
@@ -1,8 +1,7 @@
from base import api
-# requests.{post,put,get} or something like it if unavailable
-from base.interactor import post_request
-from base.interactor import put_request
-from base.interactor import get_request
+from requests import post
+from requests import put
+from requests import get
from .helpers import DatasetPopulator, wait_on
@@ -29,7 +28,7 @@
post_data = dict( name="CannotCreate" )
# Using lower-level _api_url will cause key to not be injected.
histories_url = self._api_url( "histories" )
- create_response = post_request( url=histories_url, data=post_data )
+ create_response = post( url=histories_url, data=post_data )
self._assert_status_code_is( create_response, 403 )
def test_import_export( self ):
@@ -38,7 +37,7 @@
self.dataset_populator.wait_for_history( history_id, assert_ok=True )
download_path = self._export( history_id )
full_download_url = "%s%s?key=%s" % ( self.url, download_path, self.galaxy_interactor.api_key )
- download_response = get_request( full_download_url )
+ download_response = get( full_download_url )
self._assert_status_code_is( download_response, 200 )
def history_names():
@@ -72,11 +71,11 @@
def _export(self, history_id):
export_url = self._api_url( "histories/%s/exports" % history_id, use_key=True )
- put_response = put_request( export_url )
+ put_response = put( export_url )
self._assert_status_code_is( put_response, 202 )
def export_ready_response():
- put_response = put_request( export_url )
+ put_response = put( export_url )
if put_response.status_code == 202:
return None
return put_response
diff -r d7c19ee54a9167b97f99eb83c2600f35abdea312 -r 0df64c45db90ca84e763c34919ebe9304d96f202 test/api/test_history_contents.py
--- a/test/api/test_history_contents.py
+++ b/test/api/test_history_contents.py
@@ -4,10 +4,8 @@
from .helpers import TestsDatasets
from .helpers import LibraryPopulator
from .helpers import DatasetCollectionPopulator
-from base.interactor import (
- put_request,
- delete_request,
-)
+from requests import delete
+from requests import put
# TODO: Test anonymous access.
@@ -72,7 +70,7 @@
update_url = self._api_url( "histories/%s/contents/%s" % ( self.history_id, hda1[ "id" ] ), use_key=True )
# Awkward json.dumps required here because of https://trello.com/c/CQwmCeG6
body = json.dumps( dict( deleted=True ) )
- update_response = put_request( update_url, data=body )
+ update_response = put( update_url, data=body )
self._assert_status_code_is( update_response, 200 )
show_response = self.__show( hda1 )
assert str( show_response.json()[ "deleted" ] ).lower() == "true"
@@ -82,7 +80,7 @@
self._wait_for_history( self.history_id )
assert str( self.__show( hda1 ).json()[ "deleted" ] ).lower() == "false"
url = self._api_url( "histories/%s/contents/%s" % ( self.history_id, hda1["id" ] ), use_key=True )
- delete_response = delete_request( url )
+ delete_response = delete( url )
assert delete_response.status_code < 300 # Something in the 200s :).
assert str( self.__show( hda1 ).json()[ "deleted" ] ).lower() == "true"
@@ -119,7 +117,7 @@
assert not dataset_collection[ "deleted" ]
- delete_response = delete_request( self._api_url( collection_url, use_key=True ) )
+ delete_response = delete( self._api_url( collection_url, use_key=True ) )
self._assert_status_code_is( delete_response, 200 )
show_response = self._get( collection_url )
@@ -137,7 +135,7 @@
update_url = self._api_url( "histories/%s/contents/dataset_collections/%s" % ( self.history_id, hdca[ "id" ] ), use_key=True )
# Awkward json.dumps required here because of https://trello.com/c/CQwmCeG6
body = json.dumps( dict( name="newnameforpair" ) )
- update_response = put_request( update_url, data=body )
+ update_response = put( update_url, data=body )
self._assert_status_code_is( update_response, 200 )
show_response = self.__show( hdca )
assert str( show_response.json()[ "name" ] ) == "newnameforpair"
diff -r d7c19ee54a9167b97f99eb83c2600f35abdea312 -r 0df64c45db90ca84e763c34919ebe9304d96f202 test/api/test_pages.py
--- a/test/api/test_pages.py
+++ b/test/api/test_pages.py
@@ -1,6 +1,6 @@
from galaxy.exceptions import error_codes
from base import api
-from base.interactor import delete_request
+from requests import delete
from operator import itemgetter
@@ -68,17 +68,17 @@
def test_delete( self ):
response_json = self._create_valid_page_with_slug( "testdelete" )
- delete_response = delete_request( self._api_url( "pages/%s" % response_json[ 'id' ], use_key=True ) )
+ delete_response = delete( self._api_url( "pages/%s" % response_json[ 'id' ], use_key=True ) )
self._assert_status_code_is( delete_response, 200 )
def test_404_on_delete_unknown_page( self ):
- delete_response = delete_request( self._api_url( "pages/%s" % self._random_key(), use_key=True ) )
+ delete_response = delete( self._api_url( "pages/%s" % self._random_key(), use_key=True ) )
self._assert_status_code_is( delete_response, 404 )
self._assert_error_code_is( delete_response, error_codes.USER_OBJECT_NOT_FOUND )
def test_403_on_delete_unowned_page( self ):
page_response = self._create_valid_page_as( "others_page(a)bx.psu.edu", "otherspage" )
- delete_response = delete_request( self._api_url( "pages/%s" % page_response[ "id" ], use_key=True ) )
+ delete_response = delete( self._api_url( "pages/%s" % page_response[ "id" ], use_key=True ) )
self._assert_status_code_is( delete_response, 403 )
self._assert_error_code_is( delete_response, error_codes.USER_DOES_NOT_OWN_ITEM )
diff -r d7c19ee54a9167b97f99eb83c2600f35abdea312 -r 0df64c45db90ca84e763c34919ebe9304d96f202 test/api/test_search.py
--- a/test/api/test_search.py
+++ b/test/api/test_search.py
@@ -1,5 +1,5 @@
from base import api
-from base.interactor import delete_request
+from requests import delete
from .helpers import WorkflowPopulator
@@ -14,7 +14,7 @@
# Deleted
delete_url = self._api_url( "workflows/%s" % workflow_id, use_key=True )
- delete_request( delete_url )
+ delete( delete_url )
search_response = self.__search( "select * from workflow where deleted = False" )
assert not self.__has_result_with_name( search_response, "test_for_search (imported from API)" ), search_response.json()
diff -r d7c19ee54a9167b97f99eb83c2600f35abdea312 -r 0df64c45db90ca84e763c34919ebe9304d96f202 test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -8,7 +8,8 @@
from .helpers import DatasetCollectionPopulator
from .helpers import skip_without_tool
-from base.interactor import delete_request # requests like delete
+from requests import delete
+
from galaxy.exceptions import error_codes
@@ -40,7 +41,7 @@
workflow_name = "test_delete (imported from API)"
self._assert_user_has_workflow_with_name( workflow_name )
workflow_url = self._api_url( "workflows/%s" % workflow_id, use_key=True )
- delete_response = delete_request( workflow_url )
+ delete_response = delete( workflow_url )
self._assert_status_code_is( delete_response, 200 )
# Make sure workflow is no longer in index by default.
assert workflow_name not in self.__workflow_names()
@@ -49,7 +50,7 @@
workflow_id = self.workflow_populator.simple_workflow( "test_other_delete" )
with self._different_user():
workflow_url = self._api_url( "workflows/%s" % workflow_id, use_key=True )
- delete_response = delete_request( workflow_url )
+ delete_response = delete( workflow_url )
self._assert_status_code_is( delete_response, 403 )
def test_index( self ):
diff -r d7c19ee54a9167b97f99eb83c2600f35abdea312 -r 0df64c45db90ca84e763c34919ebe9304d96f202 test/base/interactor.py
--- a/test/base/interactor.py
+++ b/test/base/interactor.py
@@ -10,7 +10,9 @@
import galaxy.model
from galaxy.model.orm import and_, desc
from functional import database_contexts
-from json import dumps, loads
+from requests import get
+from requests import post
+from json import dumps
from logging import getLogger
log = getLogger( __name__ )
@@ -366,7 +368,7 @@
key = self.api_key if not admin else self.master_api_key
data = data.copy()
data['key'] = key
- return post_request( "%s/%s" % (self.api_url, path), data=data, files=files )
+ return post( "%s/%s" % (self.api_url, path), data=data, files=files )
def _get( self, path, data={}, key=None, admin=False ):
if not key:
@@ -376,7 +378,7 @@
if path.startswith("/api"):
path = path[ len("/api"): ]
url = "%s/%s" % (self.api_url, path)
- return get_request( url, params=data )
+ return get( url, params=data )
class GalaxyInteractorTwill( object ):
@@ -496,9 +498,3 @@
'api': GalaxyInteractorApi,
'twill': GalaxyInteractorTwill,
}
-
-
-from requests import get as get_request
-from requests import post as post_request
-from requests import put as put_request
-from requests import delete as delete_request
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: dannon: Pep8 fixes in galaxy.util.json prior to work there.
by commits-noreply@bitbucket.org 04 Sep '14
by commits-noreply@bitbucket.org 04 Sep '14
04 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/6a718b389bf1/
Changeset: 6a718b389bf1
User: dannon
Date: 2014-09-04 22:58:57
Summary: Pep8 fixes in galaxy.util.json prior to work there.
Affected #: 1 file
diff -r 83e45a9e4cc994daed44e406a334660d9db7ed30 -r 6a718b389bf1caa5461c5ff7a7dd644f1279cd69 lib/galaxy/util/json.py
--- a/lib/galaxy/util/json.py
+++ b/lib/galaxy/util/json.py
@@ -5,7 +5,6 @@
import json
import logging
import random
-import socket
import string
to_json_string = json.dumps
@@ -13,6 +12,7 @@
log = logging.getLogger( __name__ )
+
def json_fix( val ):
if isinstance( val, list ):
return [ json_fix( v ) for v in val ]
@@ -25,33 +25,44 @@
# Methods for handling JSON-RPC
+
def validate_jsonrpc_request( request, regular_methods, notification_methods ):
try:
request = from_json_string( request )
except Exception, e:
- return False, request, jsonrpc_response( id = None, error = dict( code = -32700, message = 'Parse error', data = str( e ) ) )
+ return False, request, jsonrpc_response( id=None,
+ error=dict( code=-32700,
+ message='Parse error',
+ data=str( e ) ) )
try:
assert 'jsonrpc' in request, \
'This server requires JSON-RPC 2.0 and no "jsonrpc" member was sent with the Request object as per the JSON-RPC 2.0 Specification.'
assert request['jsonrpc'] == '2.0', \
- 'Requested JSON-RPC version "%s" != required version "2.0".' % request['jsonrpc']
+ 'Requested JSON-RPC version "%s" != required version "2.0".' % request['jsonrpc']
assert 'method' in request, 'No "method" member was sent with the Request object'
except AssertionError, e:
- return False, request, jsonrpc_response( request = request, error = dict( code = -32600, message = 'Invalid Request', data = str( e ) ) )
+ return False, request, jsonrpc_response( request=request,
+ error=dict( code=-32600,
+ message='Invalid Request',
+ data=str( e ) ) )
try:
assert request['method'] in ( regular_methods + notification_methods )
except AssertionError, e:
- return False, request, jsonrpc_response( request = request,
- error = dict( code = -32601,
- message = 'Method not found',
- data = 'Valid methods are: %s' % ', '.join( regular_methods + notification_methods ) ) )
+ return False, request, jsonrpc_response( request=request,
+ error=dict( code=-32601,
+ message='Method not found',
+ data='Valid methods are: %s' % ', '.join( regular_methods + notification_methods ) ) )
try:
if request['method'] in regular_methods:
assert 'id' in request, 'No "id" member was sent with the Request object and the requested method "%s" is not a notification method' % request['method']
except AssertionError, e:
- return False, request, jsonrpc_response( request = request, error = dict( code = -32600, message = 'Invalid Request', data = str( e ) ) )
+ return False, request, jsonrpc_response( request=request,
+ error=dict( code=-32600,
+ message='Invalid Request',
+ data=str( e ) ) )
return True, request, None
+
def validate_jsonrpc_response( response, id=None ):
try:
response = from_json_string( response )
@@ -81,11 +92,12 @@
return False, response
return True, response
+
def jsonrpc_request( method, params=None, id=None, jsonrpc='2.0' ):
if method is None:
log.error( 'jsonrpc_request(): "method" parameter cannot be None' )
return None
- request = dict( jsonrpc = jsonrpc, method = method )
+ request = dict( jsonrpc=jsonrpc, method=method )
if params:
request['params'] = params
if id is not None and id is True:
@@ -94,15 +106,16 @@
request['id'] = id
return request
+
def jsonrpc_response( request=None, id=None, result=None, error=None, jsonrpc='2.0' ):
if result:
- rval = dict( jsonrpc = jsonrpc, result = result )
+ rval = dict( jsonrpc=jsonrpc, result=result )
elif error:
- rval = dict( jsonrpc = jsonrpc, error = error )
+ rval = dict( jsonrpc=jsonrpc, error=error )
else:
msg = 'jsonrpc_response() called with out a "result" or "error" parameter'
log.error( msg )
- rval = dict( jsonrpc = jsonrpc, error = msg )
+ rval = dict( jsonrpc=jsonrpc, error=msg )
if id is not None:
rval['id'] = id
elif request is not None and 'id' in request:
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
04 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/901144086f2e/
Changeset: 901144086f2e
Branch: venv
User: dannon
Date: 2014-09-04 22:44:25
Summary: Close feature branch
Affected #: 0 files
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: dannon: Merged in carlfeberhard/galaxy-central-fork0 (pull request #484)
by commits-noreply@bitbucket.org 04 Sep '14
by commits-noreply@bitbucket.org 04 Sep '14
04 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/83e45a9e4cc9/
Changeset: 83e45a9e4cc9
User: dannon
Date: 2014-09-04 22:30:50
Summary: Merged in carlfeberhard/galaxy-central-fork0 (pull request #484)
Break up web/framework/__init__ into more focused modules
Affected #: 20 files
diff -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f -r 83e45a9e4cc994daed44e406a334660d9db7ed30 lib/galaxy/datatypes/metadata.py
--- a/lib/galaxy/datatypes/metadata.py
+++ b/lib/galaxy/datatypes/metadata.py
@@ -6,7 +6,6 @@
import copy
import cPickle
import json
-import logging
import os
import shutil
import sys
@@ -15,13 +14,19 @@
from os.path import abspath
-import galaxy.model
-from galaxy.util import listify, stringify_dictionary_keys, string_as_bool
-from galaxy.util.odict import odict
-from galaxy.util import in_directory
-from galaxy.web import form_builder
+from galaxy import eggs
+eggs.require( "SQLAlchemy >= 0.4" )
from sqlalchemy.orm import object_session
+import galaxy.model
+from galaxy.util import listify
+from galaxy.util import stringify_dictionary_keys
+from galaxy.util import string_as_bool
+from galaxy.util import in_directory
+from galaxy.util.odict import odict
+from galaxy.web import form_builder
+
+import logging
log = logging.getLogger(__name__)
STATEMENTS = "__galaxy_statements__" #this is the name of the property in a Datatype class where new metadata spec element Statements are stored
diff -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f -r 83e45a9e4cc994daed44e406a334660d9db7ed30 lib/galaxy/managers/context.py
--- /dev/null
+++ b/lib/galaxy/managers/context.py
@@ -0,0 +1,179 @@
+"""
+Mixins for transaction-like objects.
+"""
+
+import os
+
+from galaxy.util.json import to_json_string
+from galaxy.util import bunch
+
+class ProvidesAppContext( object ):
+ """ For transaction-like objects to provide Galaxy convience layer for
+ database and event handling.
+
+ Mixed in class must provide `app` property.
+ """
+
+ def log_action( self, user=None, action=None, context=None, params=None):
+ """
+ Application-level logging of user actions.
+ """
+ if self.app.config.log_actions:
+ action = self.app.model.UserAction(action=action, context=context, params=unicode( to_json_string( params ) ) )
+ try:
+ if user:
+ action.user = user
+ else:
+ action.user = self.user
+ except:
+ action.user = None
+ try:
+ action.session_id = self.galaxy_session.id
+ except:
+ action.session_id = None
+ self.sa_session.add( action )
+ self.sa_session.flush()
+
+ def log_event( self, message, tool_id=None, **kwargs ):
+ """
+ Application level logging. Still needs fleshing out (log levels and such)
+ Logging events is a config setting - if False, do not log.
+ """
+ if self.app.config.log_events:
+ event = self.app.model.Event()
+ event.tool_id = tool_id
+ try:
+ event.message = message % kwargs
+ except:
+ event.message = message
+ try:
+ event.history = self.get_history()
+ except:
+ event.history = None
+ try:
+ event.history_id = self.history.id
+ except:
+ event.history_id = None
+ try:
+ event.user = self.user
+ except:
+ event.user = None
+ try:
+ event.session_id = self.galaxy_session.id
+ except:
+ event.session_id = None
+ self.sa_session.add( event )
+ self.sa_session.flush()
+
+ @property
+ def sa_session( self ):
+ """
+ Returns a SQLAlchemy session -- currently just gets the current
+ session from the threadlocal session context, but this is provided
+ to allow migration toward a more SQLAlchemy 0.4 style of use.
+ """
+ return self.app.model.context.current
+
+ def expunge_all( self ):
+ app = self.app
+ context = app.model.context
+ context.expunge_all()
+ # This is a bit hacky, should refctor this. Maybe refactor to app -> expunge_all()
+ if hasattr(app, 'install_model'):
+ install_model = app.install_model
+ if install_model != app.model:
+ install_model.context.expunge_all()
+
+ def get_toolbox(self):
+ """Returns the application toolbox"""
+ return self.app.toolbox
+
+ @property
+ def model( self ):
+ return self.app.model
+
+ @property
+ def install_model( self ):
+ return self.app.install_model
+
+ def request_types(self):
+ if self.sa_session.query( self.app.model.RequestType ).filter_by( deleted=False ).count() > 0:
+ return True
+ return False
+
+
+class ProvidesUserContext( object ):
+ """ For transaction-like objects to provide Galaxy convience layer for
+ reasoning about users.
+
+ Mixed in class must provide `user`, `api_inherit_admin`, and `app`
+ properties.
+ """
+
+ @property
+ def anonymous( self ):
+ return self.user is None and not self.api_inherit_admin
+
+ def get_current_user_roles( self ):
+ user = self.user
+ if user:
+ roles = user.all_roles()
+ else:
+ roles = []
+ return roles
+
+ def user_is_admin( self ):
+ if self.api_inherit_admin:
+ return True
+ return self.user and self.user.email in self.app.config.admin_users_list
+
+ def user_can_do_run_as( self ):
+ run_as_users = [ user for user in self.app.config.get( "api_allow_run_as", "" ).split( "," ) if user ]
+ if not run_as_users:
+ return False
+ user_in_run_as_users = self.user and self.user.email in run_as_users
+ # Can do if explicitly in list or master_api_key supplied.
+ can_do_run_as = user_in_run_as_users or self.api_inherit_admin
+ return can_do_run_as
+
+ @property
+ def user_ftp_dir( self ):
+ identifier = self.app.config.ftp_upload_dir_identifier
+ return os.path.join( self.app.config.ftp_upload_dir, getattr( self.user, identifier ) )
+
+
+class ProvidesHistoryContext( object ):
+ """ For transaction-like objects to provide Galaxy convience layer for
+ reasoning about histories.
+
+ Mixed in class must provide `user`, `history`, and `app`
+ properties.
+ """
+
+ def db_dataset_for( self, dbkey ):
+ """
+ Returns the db_file dataset associated/needed by `dataset`, or `None`.
+ """
+ # If no history, return None.
+ if self.history is None:
+ return None
+#TODO: when does this happen? is it Bunch or util.bunch.Bunch?
+ if isinstance( self.history, bunch.Bunch ):
+ # The API presents a Bunch for a history. Until the API is
+ # more fully featured for handling this, also return None.
+ return None
+ datasets = self.sa_session.query( self.app.model.HistoryDatasetAssociation ) \
+ .filter_by( deleted=False, history_id=self.history.id, extension="len" )
+ for ds in datasets:
+ if dbkey == ds.dbkey:
+ return ds
+ return None
+
+ @property
+ def db_builds( self ):
+ """
+ Returns the builds defined by galaxy and the builds defined by
+ the user (chromInfo in history).
+ """
+ # FIXME: This method should be removed
+ return self.app.genome_builds.get_genome_build_names( trans=self )
diff -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f -r 83e45a9e4cc994daed44e406a334660d9db7ed30 lib/galaxy/web/__init__.py
--- a/lib/galaxy/web/__init__.py
+++ b/lib/galaxy/web/__init__.py
@@ -1,23 +1,25 @@
"""
The Galaxy web application framework
"""
-from framework import expose
-from framework import json
-from framework import json_pretty
-from framework import require_login
-from framework import require_admin
from framework import url_for
-from framework import error
-from framework import form
-from framework import FormBuilder
-from framework import expose_api
-from framework import expose_api_anonymous
-from framework import expose_api_raw
-from framework import expose_api_raw_anonymous
-from framework.base import httpexceptions
+from framework.decorators import error
+from framework.decorators import expose
+from framework.decorators import json
+from framework.decorators import json_pretty
+from framework.decorators import require_login
+from framework.decorators import require_admin
+from framework.decorators import expose_api
+from framework.decorators import expose_api_anonymous
+from framework.decorators import expose_api_raw
+from framework.decorators import expose_api_raw_anonymous
# TODO: Drop and make these the default.
-from framework import _future_expose_api
-from framework import _future_expose_api_anonymous
-from framework import _future_expose_api_raw
-from framework import _future_expose_api_raw_anonymous
+from framework.decorators import _future_expose_api
+from framework.decorators import _future_expose_api_anonymous
+from framework.decorators import _future_expose_api_raw
+from framework.decorators import _future_expose_api_raw_anonymous
+
+from framework.formbuilder import form
+from framework.formbuilder import FormBuilder
+
+from framework.base import httpexceptions
diff -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f -r 83e45a9e4cc994daed44e406a334660d9db7ed30 lib/galaxy/web/base/pluginframework.py
--- a/lib/galaxy/web/base/pluginframework.py
+++ b/lib/galaxy/web/base/pluginframework.py
@@ -579,7 +579,7 @@
"""
Pass control over to trans and render ``template_filename``.
- :type trans: ``galaxy.web.framework.GalaxyWebTransaction``
+ :type trans: ``galaxy.web.framework.webapp.GalaxyWebTransaction``
:param trans: transaction doing the rendering
:type plugin: ``util.bunch.Bunch``
:param plugin: the plugin containing the template to render
diff -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f -r 83e45a9e4cc994daed44e406a334660d9db7ed30 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -2,1395 +2,9 @@
Galaxy web application framework
"""
-import hashlib
-import inspect
-import os
-import random
-import socket
-import string
-import time
-from traceback import format_exc
-from Cookie import CookieError
-from functools import wraps
-
from galaxy import eggs
-
-eggs.require( "Cheetah" )
-from Cheetah.Template import Template
-
-from galaxy import util
-from galaxy.exceptions import error_codes
-from galaxy.exceptions import MessageException
-from galaxy.util import asbool
-from galaxy.util import safe_str_cmp
-from galaxy.util.backports.importlib import import_module
-from galaxy.util.json import from_json_string, to_json_string
-from galaxy.util.sanitize_html import sanitize_html
-from galaxy.web.framework import base, helpers
-
-import paste.httpexceptions
-
-eggs.require( "Mako" )
-import mako.template
-import mako.lookup
-import mako.runtime
-
-eggs.require( "pytz" ) # Used by Babel.
-eggs.require( "Babel" )
-from babel.support import Translations
-from babel import Locale
-
-eggs.require( "SQLAlchemy >= 0.4" )
-from sqlalchemy import and_
-from sqlalchemy.orm.exc import NoResultFound
-
eggs.require( "pexpect" )
eggs.require( "amqp" )
-import logging
-log = logging.getLogger( __name__ )
-
+import base
url_for = base.routes.url_for
-
-UCSC_SERVERS = (
- 'hgw1.cse.ucsc.edu',
- 'hgw2.cse.ucsc.edu',
- 'hgw3.cse.ucsc.edu',
- 'hgw4.cse.ucsc.edu',
- 'hgw5.cse.ucsc.edu',
- 'hgw6.cse.ucsc.edu',
- 'hgw7.cse.ucsc.edu',
- 'hgw8.cse.ucsc.edu',
-)
-
-JSON_CONTENT_TYPE = "application/json"
-
-
-# ----------------------------------------------------------------------------- web controller decorators
-def expose( func ):
- """
- Decorator: mark a function as 'exposed' and thus web accessible
- """
- func.exposed = True
- return func
-
-def json( func, **json_kwargs ):
- """
- Format the response as JSON and set the response content type to
- JSON_CONTENT_TYPE.
- """
- @wraps(func)
- def call_and_format( self, trans, *args, **kwargs ):
- trans.response.set_content_type( JSON_CONTENT_TYPE )
- return to_json_string( func( self, trans, *args, **kwargs ), **json_kwargs )
- if not hasattr(func, '_orig'):
- call_and_format._orig = func
- call_and_format.exposed = True
- return call_and_format
-
-def json_pretty( func ):
- """
- Indent and sort returned JSON.
- """
- return json( func, indent=4, sort_keys=True )
-
-def require_login( verb="perform this action", use_panels=False, webapp='galaxy' ):
- def argcatcher( func ):
- @wraps(func)
- def decorator( self, trans, *args, **kwargs ):
- if trans.get_user():
- return func( self, trans, *args, **kwargs )
- else:
- return trans.show_error_message(
- 'You must be <a target="galaxy_main" href="%s">logged in</a> to %s.'
- % ( url_for( controller='user', action='login', webapp=webapp ), verb ), use_panels=use_panels )
- return decorator
- return argcatcher
-
-def require_admin( func ):
- @wraps(func)
- def decorator( self, trans, *args, **kwargs ):
- if not trans.user_is_admin():
- msg = "You must be an administrator to access this feature."
- user = trans.get_user()
- if not trans.app.config.admin_users_list:
- msg = "You must be logged in as an administrator to access this feature, but no administrators are set in the Galaxy configuration."
- elif not user:
- msg = "You must be logged in as an administrator to access this feature."
- trans.response.status = 403
- if trans.response.get_content_type() == 'application/json':
- return msg
- else:
- return trans.show_error_message( msg )
- return func( self, trans, *args, **kwargs )
- return decorator
-
-
-# ----------------------------------------------------------------------------- (original) api decorators
-def expose_api( func, to_json=True, user_required=True ):
- """
- Expose this function via the API.
- """
- @wraps(func)
- def decorator( self, trans, *args, **kwargs ):
- def error( environ, start_response ):
- start_response( error_status, [('Content-type', 'text/plain')] )
- return error_message
- error_status = '403 Forbidden'
- if trans.error_message:
- return trans.error_message
- if user_required and trans.anonymous:
- error_message = "API Authentication Required for this request"
- return error
- if trans.request.body:
- try:
- kwargs['payload'] = __extract_payload_from_request(trans, func, kwargs)
- except ValueError:
- error_status = '400 Bad Request'
- error_message = 'Your request did not appear to be valid JSON, please consult the API documentation'
- return error
- trans.response.set_content_type( "application/json" )
- # send 'do not cache' headers to handle IE's caching of ajax get responses
- trans.response.headers[ 'Cache-Control' ] = "max-age=0,no-cache,no-store"
- # Perform api_run_as processing, possibly changing identity
- if 'payload' in kwargs and 'run_as' in kwargs['payload']:
- if not trans.user_can_do_run_as():
- error_message = 'User does not have permissions to run jobs as another user'
- return error
- try:
- decoded_user_id = trans.security.decode_id( kwargs['payload']['run_as'] )
- except TypeError:
- trans.response.status = 400
- return "Malformed user id ( %s ) specified, unable to decode." % str( kwargs['payload']['run_as'] )
- try:
- user = trans.sa_session.query( trans.app.model.User ).get( decoded_user_id )
- trans.api_inherit_admin = trans.user_is_admin()
- trans.set_user(user)
- except:
- trans.response.status = 400
- return "That user does not exist."
- try:
- rval = func( self, trans, *args, **kwargs)
- if to_json and trans.debug:
- rval = to_json_string( rval, indent=4, sort_keys=True )
- elif to_json:
- rval = to_json_string( rval )
- return rval
- except paste.httpexceptions.HTTPException:
- raise # handled
- except:
- log.exception( 'Uncaught exception in exposed API method:' )
- raise paste.httpexceptions.HTTPServerError()
- if not hasattr(func, '_orig'):
- decorator._orig = func
- decorator.exposed = True
- return decorator
-
-def __extract_payload_from_request(trans, func, kwargs):
- content_type = trans.request.headers['content-type']
- if content_type.startswith('application/x-www-form-urlencoded') or content_type.startswith('multipart/form-data'):
- # If the content type is a standard type such as multipart/form-data, the wsgi framework parses the request body
- # and loads all field values into kwargs. However, kwargs also contains formal method parameters etc. which
- # are not a part of the request body. This is a problem because it's not possible to differentiate between values
- # which are a part of the request body, and therefore should be a part of the payload, and values which should not be
- # in the payload. Therefore, the decorated method's formal arguments are discovered through reflection and removed from
- # the payload dictionary. This helps to prevent duplicate argument conflicts in downstream methods.
- payload = kwargs.copy()
- named_args, _, _, _ = inspect.getargspec(func)
- for arg in named_args:
- payload.pop(arg, None)
- for k, v in payload.iteritems():
- if isinstance(v, (str, unicode)):
- try:
- payload[k] = from_json_string(v)
- except:
- # may not actually be json, just continue
- pass
- payload = util.recursively_stringify_dictionary_keys( payload )
- else:
- # Assume application/json content type and parse request body manually, since wsgi won't do it. However, the order of this check
- # should ideally be in reverse, with the if clause being a check for application/json and the else clause assuming a standard encoding
- # such as multipart/form-data. Leaving it as is for backward compatibility, just in case.
- payload = util.recursively_stringify_dictionary_keys( from_json_string( trans.request.body ) )
- return payload
-
-def expose_api_raw( func ):
- """
- Expose this function via the API but don't dump the results
- to JSON.
- """
- return expose_api( func, to_json=False )
-
-def expose_api_raw_anonymous( func ):
- """
- Expose this function via the API but don't dump the results
- to JSON.
- """
- return expose_api( func, to_json=False, user_required=False )
-
-def expose_api_anonymous( func, to_json=True ):
- """
- Expose this function via the API but don't require a set user.
- """
- return expose_api( func, to_json=to_json, user_required=False )
-
-
-# ----------------------------------------------------------------------------- (new) api decorators
-# TODO: rename as expose_api and make default.
-def _future_expose_api( func, to_json=True, user_required=True ):
- """
- Expose this function via the API.
- """
- @wraps(func)
- def decorator( self, trans, *args, **kwargs ):
- if trans.error_message:
- # TODO: Document this branch, when can this happen,
- # I don't understand it.
- return __api_error_response( trans, err_msg=trans.error_message )
- if user_required and trans.anonymous:
- error_code = error_codes.USER_NO_API_KEY
- # Use error codes default error message.
- err_msg = "API authentication required for this request"
- return __api_error_response( trans, err_code=error_code, err_msg=err_msg, status_code=403 )
- if trans.request.body:
- try:
- kwargs['payload'] = __extract_payload_from_request(trans, func, kwargs)
- except ValueError:
- error_code = error_codes.USER_INVALID_JSON
- return __api_error_response( trans, status_code=400, err_code=error_code )
-
- trans.response.set_content_type( JSON_CONTENT_TYPE )
- # send 'do not cache' headers to handle IE's caching of ajax get responses
- trans.response.headers[ 'Cache-Control' ] = "max-age=0,no-cache,no-store"
- # TODO: Refactor next block out into a helper procedure.
- # Perform api_run_as processing, possibly changing identity
- if 'payload' in kwargs and 'run_as' in kwargs['payload']:
- if not trans.user_can_do_run_as():
- error_code = error_codes.USER_CANNOT_RUN_AS
- return __api_error_response( trans, err_code=error_code, status_code=403 )
- try:
- decoded_user_id = trans.security.decode_id( kwargs['payload']['run_as'] )
- except TypeError:
- error_message = "Malformed user id ( %s ) specified, unable to decode." % str( kwargs['payload']['run_as'] )
- error_code = error_codes.USER_INVALID_RUN_AS
- return __api_error_response( trans, err_code=error_code, err_msg=error_message, status_code=400)
- try:
- user = trans.sa_session.query( trans.app.model.User ).get( decoded_user_id )
- trans.api_inherit_admin = trans.user_is_admin()
- trans.set_user(user)
- except:
- error_code = error_codes.USER_INVALID_RUN_AS
- return __api_error_response( trans, err_code=error_code, status_code=400 )
- try:
- rval = func( self, trans, *args, **kwargs)
- if to_json and trans.debug:
- rval = to_json_string( rval, indent=4, sort_keys=True )
- elif to_json:
- rval = to_json_string( rval )
- return rval
- except MessageException as e:
- traceback_string = format_exc()
- return __api_error_response( trans, exception=e, traceback=traceback_string )
- except paste.httpexceptions.HTTPException:
- # TODO: Allow to pass or format for the API???
- raise # handled
- except Exception as e:
- traceback_string = format_exc()
- error_message = 'Uncaught exception in exposed API method:'
- log.exception( error_message )
- return __api_error_response(
- trans,
- status_code=500,
- exception=e,
- traceback=traceback_string,
- err_msg=error_message,
- err_code=error_codes.UNKNOWN
- )
- if not hasattr(func, '_orig'):
- decorator._orig = func
- decorator.exposed = True
- return decorator
-
-def __api_error_message( trans, **kwds ):
- exception = kwds.get( "exception", None )
- if exception:
- # If we are passed a MessageException use err_msg.
- default_error_code = getattr( exception, "err_code", error_codes.UNKNOWN )
- default_error_message = getattr( exception, "err_msg", default_error_code.default_error_message )
- extra_error_info = getattr( exception, 'extra_error_info', {} )
- if not isinstance( extra_error_info, dict ):
- extra_error_info = {}
- else:
- default_error_message = "Error processing API request."
- default_error_code = error_codes.UNKNOWN
- extra_error_info = {}
- traceback_string = kwds.get( "traceback", "No traceback available." )
- err_msg = kwds.get( "err_msg", default_error_message )
- error_code_object = kwds.get( "err_code", default_error_code )
- try:
- error_code = error_code_object.code
- except AttributeError:
- # Some sort of bad error code sent in, logic failure on part of
- # Galaxy developer.
- error_code = error_codes.UNKNOWN.code
- # Would prefer the terminology of error_code and error_message, but
- # err_msg used a good number of places already. Might as well not change
- # it?
- error_response = dict( err_msg=err_msg, err_code=error_code, **extra_error_info )
- if trans.debug: # TODO: Should admins get to see traceback as well?
- error_response[ "traceback" ] = traceback_string
- return error_response
-
-def __api_error_response( trans, **kwds ):
- error_dict = __api_error_message( trans, **kwds )
- exception = kwds.get( "exception", None )
- # If we are given an status code directly - use it - otherwise check
- # the exception for a status_code attribute.
- if "status_code" in kwds:
- status_code = int( kwds.get( "status_code" ) )
- elif hasattr( exception, "status_code" ):
- status_code = int( exception.status_code )
- else:
- status_code = 500
- response = trans.response
- if not response.status or str(response.status).startswith("20"):
- # Unset status code appears to be string '200 OK', if anything
- # non-success (i.e. not 200 or 201) has been set, do not override
- # underlying controller.
- response.status = status_code
- return to_json_string( error_dict )
-
-
-# TODO: rename as expose_api and make default.
-def _future_expose_api_anonymous( func, to_json=True ):
- """
- Expose this function via the API but don't require a set user.
- """
- return _future_expose_api( func, to_json=to_json, user_required=False )
-
-
-def _future_expose_api_raw( func ):
- return _future_expose_api( func, to_json=False, user_required=True )
-
-
-def _future_expose_api_raw_anonymous( func ):
- return _future_expose_api( func, to_json=False, user_required=False )
-
-
-
-NOT_SET = object()
-
-
-def error( message ):
- raise MessageException( message, type='error' )
-
-
-def form( *args, **kwargs ):
- return FormBuilder( *args, **kwargs )
-
-
-class WebApplication( base.WebApplication ):
-
- def __init__( self, galaxy_app, session_cookie='galaxysession', name=None ):
- self.name = name
- base.WebApplication.__init__( self )
- self.set_transaction_factory( lambda e: self.transaction_chooser( e, galaxy_app, session_cookie ) )
- # Mako support
- self.mako_template_lookup = self.create_mako_template_lookup( galaxy_app, name )
- # Security helper
- self.security = galaxy_app.security
-
- def create_mako_template_lookup( self, galaxy_app, name ):
- paths = []
- # First look in webapp specific directory
- if name is not None:
- paths.append( os.path.join( galaxy_app.config.template_path, 'webapps', name ) )
- # Then look in root directory
- paths.append( galaxy_app.config.template_path )
- # Create TemplateLookup with a small cache
- return mako.lookup.TemplateLookup(directories=paths,
- module_directory=galaxy_app.config.template_cache,
- collection_size=500,
- output_encoding='utf-8' )
-
- def handle_controller_exception( self, e, trans, **kwargs ):
- if isinstance( e, MessageException ):
- # In the case of a controller exception, sanitize to make sure
- # unsafe html input isn't reflected back to the user
- return trans.show_message( sanitize_html(e.err_msg), e.type )
-
- def make_body_iterable( self, trans, body ):
- if isinstance( body, FormBuilder ):
- body = trans.show_form( body )
- return base.WebApplication.make_body_iterable( self, trans, body )
-
- def transaction_chooser( self, environ, galaxy_app, session_cookie ):
- return GalaxyWebTransaction( environ, galaxy_app, self, session_cookie )
-
- def add_ui_controllers( self, package_name, app ):
- """
- Search for UI controllers in `package_name` and add
- them to the webapp.
- """
- from galaxy.web.base.controller import BaseUIController
- from galaxy.web.base.controller import ControllerUnavailable
- package = import_module( package_name )
- controller_dir = package.__path__[0]
- for fname in os.listdir( controller_dir ):
- if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
- name = fname[:-3]
- module_name = package_name + "." + name
- try:
- module = import_module( module_name )
- except ControllerUnavailable, exc:
- log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
- continue
- # Look for a controller inside the modules
- for key in dir( module ):
- T = getattr( module, key )
- if inspect.isclass( T ) and T is not BaseUIController and issubclass( T, BaseUIController ):
- controller = self._instantiate_controller( T, app )
- self.add_ui_controller( name, controller )
-
- def add_api_controllers( self, package_name, app ):
- """
- Search for UI controllers in `package_name` and add
- them to the webapp.
- """
- from galaxy.web.base.controller import BaseAPIController
- from galaxy.web.base.controller import ControllerUnavailable
- package = import_module( package_name )
- controller_dir = package.__path__[0]
- for fname in os.listdir( controller_dir ):
- if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
- name = fname[:-3]
- module_name = package_name + "." + name
- try:
- module = import_module( module_name )
- except ControllerUnavailable, exc:
- log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
- continue
- for key in dir( module ):
- T = getattr( module, key )
- # Exclude classes such as BaseAPIController and BaseTagItemsController
- if inspect.isclass( T ) and not key.startswith("Base") and issubclass( T, BaseAPIController ):
- # By default use module_name, but allow controller to override name
- controller_name = getattr( T, "controller_name", name )
- controller = self._instantiate_controller( T, app )
- self.add_api_controller( controller_name, controller )
-
- def _instantiate_controller( self, T, app ):
- """ Extension point, allow apps to contstruct controllers differently,
- really just used to stub out actual controllers for routes testing.
- """
- return T( app )
-
-
-class ProvidesAppContext( object ):
- """ For transaction-like objects to provide Galaxy convience layer for
- database and event handling.
-
- Mixed in class must provide `app` property.
- """
-
- def log_action( self, user=None, action=None, context=None, params=None):
- """
- Application-level logging of user actions.
- """
- if self.app.config.log_actions:
- action = self.app.model.UserAction(action=action, context=context, params=unicode( to_json_string( params ) ) )
- try:
- if user:
- action.user = user
- else:
- action.user = self.user
- except:
- action.user = None
- try:
- action.session_id = self.galaxy_session.id
- except:
- action.session_id = None
- self.sa_session.add( action )
- self.sa_session.flush()
-
- def log_event( self, message, tool_id=None, **kwargs ):
- """
- Application level logging. Still needs fleshing out (log levels and such)
- Logging events is a config setting - if False, do not log.
- """
- if self.app.config.log_events:
- event = self.app.model.Event()
- event.tool_id = tool_id
- try:
- event.message = message % kwargs
- except:
- event.message = message
- try:
- event.history = self.get_history()
- except:
- event.history = None
- try:
- event.history_id = self.history.id
- except:
- event.history_id = None
- try:
- event.user = self.user
- except:
- event.user = None
- try:
- event.session_id = self.galaxy_session.id
- except:
- event.session_id = None
- self.sa_session.add( event )
- self.sa_session.flush()
-
- @property
- def sa_session( self ):
- """
- Returns a SQLAlchemy session -- currently just gets the current
- session from the threadlocal session context, but this is provided
- to allow migration toward a more SQLAlchemy 0.4 style of use.
- """
- return self.app.model.context.current
-
- def expunge_all( self ):
- app = self.app
- context = app.model.context
- context.expunge_all()
- # This is a bit hacky, should refctor this. Maybe refactor to app -> expunge_all()
- if hasattr(app, 'install_model'):
- install_model = app.install_model
- if install_model != app.model:
- install_model.context.expunge_all()
-
- def get_toolbox(self):
- """Returns the application toolbox"""
- return self.app.toolbox
-
- @property
- def model( self ):
- return self.app.model
-
- @property
- def install_model( self ):
- return self.app.install_model
-
- def request_types(self):
- if self.sa_session.query( self.app.model.RequestType ).filter_by( deleted=False ).count() > 0:
- return True
- return False
-
-
-class ProvidesUserContext( object ):
- """ For transaction-like objects to provide Galaxy convience layer for
- reasoning about users.
-
- Mixed in class must provide `user`, `api_inherit_admin`, and `app`
- properties.
- """
-
- @property
- def anonymous( self ):
- return self.user is None and not self.api_inherit_admin
-
- def get_current_user_roles( self ):
- user = self.user
- if user:
- roles = user.all_roles()
- else:
- roles = []
- return roles
-
- def user_is_admin( self ):
- if self.api_inherit_admin:
- return True
- return self.user and self.user.email in self.app.config.admin_users_list
-
- def user_can_do_run_as( self ):
- run_as_users = [ user for user in self.app.config.get( "api_allow_run_as", "" ).split( "," ) if user ]
- if not run_as_users:
- return False
- user_in_run_as_users = self.user and self.user.email in run_as_users
- # Can do if explicitly in list or master_api_key supplied.
- can_do_run_as = user_in_run_as_users or self.api_inherit_admin
- return can_do_run_as
-
- @property
- def user_ftp_dir( self ):
- identifier = self.app.config.ftp_upload_dir_identifier
- return os.path.join( self.app.config.ftp_upload_dir, getattr(self.user, identifier) )
-
-
-class ProvidesHistoryContext( object ):
- """ For transaction-like objects to provide Galaxy convience layer for
- reasoning about histories.
-
- Mixed in class must provide `user`, `history`, and `app`
- properties.
- """
-
- def db_dataset_for( self, dbkey ):
- """
- Returns the db_file dataset associated/needed by `dataset`, or `None`.
- """
- # If no history, return None.
- if self.history is None:
- return None
- if isinstance(self.history, Bunch):
- # The API presents a Bunch for a history. Until the API is
- # more fully featured for handling this, also return None.
- return None
- datasets = self.sa_session.query( self.app.model.HistoryDatasetAssociation ) \
- .filter_by( deleted=False, history_id=self.history.id, extension="len" )
- for ds in datasets:
- if dbkey == ds.dbkey:
- return ds
- return None
-
- @property
- def db_builds( self ):
- """
- Returns the builds defined by galaxy and the builds defined by
- the user (chromInfo in history).
- """
- # FIXME: This method should be removed
- return self.app.genome_builds.get_genome_build_names( trans=self )
-
-
-class GalaxyWebTransaction( base.DefaultWebTransaction, ProvidesAppContext, ProvidesUserContext, ProvidesHistoryContext ):
- """
- Encapsulates web transaction specific state for the Galaxy application
- (specifically the user's "cookie" session and history)
- """
-
- def __init__( self, environ, app, webapp, session_cookie=None):
- self.app = app
- self.webapp = webapp
- self.security = webapp.security
- base.DefaultWebTransaction.__init__( self, environ )
- self.setup_i18n()
- self.expunge_all()
- self.debug = asbool( self.app.config.get( 'debug', False ) )
- # Flag indicating whether we are in workflow building mode (means
- # that the current history should not be used for parameter values
- # and such).
- self.workflow_building_mode = False
- # Flag indicating whether this is an API call and the API key user is an administrator
- self.api_inherit_admin = False
- self.__user = None
- self.galaxy_session = None
- self.error_message = None
-
- if self.environ.get('is_api_request', False):
- # With API requests, if there's a key, use it and associate the
- # user with the transaction.
- # If not, check for an active session but do not create one.
- # If an error message is set here, it's sent back using
- # trans.show_error in the response -- in expose_api.
- self.error_message = self._authenticate_api( session_cookie )
- elif self.app.name == "reports":
- self.galaxy_session = None
- else:
- # This is a web request, get or create session.
- self._ensure_valid_session( session_cookie )
- if self.galaxy_session:
- # When we've authenticated by session, we have to check the
- # following.
- # Prevent deleted users from accessing Galaxy
- if self.app.config.use_remote_user and self.galaxy_session.user.deleted:
- self.response.send_redirect( url_for( '/static/user_disabled.html' ) )
- if self.app.config.require_login:
- self._ensure_logged_in_user( environ, session_cookie )
-
- def setup_i18n( self ):
- locales = []
- if 'HTTP_ACCEPT_LANGUAGE' in self.environ:
- # locales looks something like: ['en', 'en-us;q=0.7', 'ja;q=0.3']
- client_locales = self.environ['HTTP_ACCEPT_LANGUAGE'].split( ',' )
- for locale in client_locales:
- try:
- locales.append( Locale.parse( locale.split( ';' )[0].strip(), sep='-' ).language )
- except Exception, e:
- log.debug( "Error parsing locale '%s'. %s: %s", locale, type( e ), e )
- if not locales:
- # Default to English
- locales = 'en'
- t = Translations.load( dirname='locale', locales=locales, domain='ginga' )
- self.template_context.update( dict( _=t.ugettext, n_=t.ugettext, N_=t.ungettext ) )
-
- def get_user( self ):
- """Return the current user if logged in or None."""
- if self.galaxy_session:
- return self.galaxy_session.user
- else:
- return self.__user
-
- def set_user( self, user ):
- """Set the current user."""
- if self.galaxy_session:
- self.galaxy_session.user = user
- self.sa_session.add( self.galaxy_session )
- self.sa_session.flush()
- self.__user = user
-
- user = property( get_user, set_user )
-
- def get_cookie( self, name='galaxysession' ):
- """Convenience method for getting a session cookie"""
- try:
- # If we've changed the cookie during the request return the new value
- if name in self.response.cookies:
- return self.response.cookies[name].value
- else:
- return self.request.cookies[name].value
- except:
- return None
-
- def set_cookie( self, value, name='galaxysession', path='/', age=90, version='1' ):
- """Convenience method for setting a session cookie"""
- # The galaxysession cookie value must be a high entropy 128 bit random number encrypted
- # using a server secret key. Any other value is invalid and could pose security issues.
- self.response.cookies[name] = value
- self.response.cookies[name]['path'] = path
- self.response.cookies[name]['max-age'] = 3600 * 24 * age # 90 days
- tstamp = time.localtime( time.time() + 3600 * 24 * age )
- self.response.cookies[name]['expires'] = time.strftime( '%a, %d-%b-%Y %H:%M:%S GMT', tstamp )
- self.response.cookies[name]['version'] = version
- try:
- self.response.cookies[name]['httponly'] = True
- except CookieError, e:
- log.warning( "Error setting httponly attribute in cookie '%s': %s" % ( name, e ) )
-
- def _authenticate_api( self, session_cookie ):
- """
- Authenticate for the API via key or session (if available).
- """
- api_key = self.request.params.get('key', None)
- secure_id = self.get_cookie( name=session_cookie )
- api_key_supplied = self.environ.get('is_api_request', False) and api_key
- if api_key_supplied and self._check_master_api_key( api_key ):
- self.api_inherit_admin = True
- log.info( "Session authenticated using Galaxy master api key" )
- self.user = None
- self.galaxy_session = None
- elif api_key_supplied:
- # Sessionless API transaction, we just need to associate a user.
- try:
- provided_key = self.sa_session.query( self.app.model.APIKeys ).filter( self.app.model.APIKeys.table.c.key == api_key ).one()
- except NoResultFound:
- return 'Provided API key is not valid.'
- if provided_key.user.deleted:
- return 'User account is deactivated, please contact an administrator.'
- newest_key = provided_key.user.api_keys[0]
- if newest_key.key != provided_key.key:
- return 'Provided API key has expired.'
- self.set_user( provided_key.user )
- elif secure_id:
- # API authentication via active session
- # Associate user using existing session
- self._ensure_valid_session( session_cookie )
- else:
- # Anonymous API interaction -- anything but @expose_api_anonymous will fail past here.
- self.user = None
- self.galaxy_session = None
-
- def _check_master_api_key( self, api_key ):
- master_api_key = getattr( self.app.config, 'master_api_key', None )
- if not master_api_key:
- return False
- # Hash keys to make them the same size, so we can do safe comparison.
- master_hash = hashlib.sha256( master_api_key ).hexdigest()
- provided_hash = hashlib.sha256( api_key ).hexdigest()
- return safe_str_cmp( master_hash, provided_hash )
-
- def _ensure_valid_session( self, session_cookie, create=True):
- """
- Ensure that a valid Galaxy session exists and is available as
- trans.session (part of initialization)
-
- Support for universe_session and universe_user cookies has been
- removed as of 31 Oct 2008.
- """
- # Try to load an existing session
- secure_id = self.get_cookie( name=session_cookie )
- galaxy_session = None
- prev_galaxy_session = None
- user_for_new_session = None
- invalidate_existing_session = False
- # Track whether the session has changed so we can avoid calling flush
- # in the most common case (session exists and is valid).
- galaxy_session_requires_flush = False
- if secure_id:
- # Decode the cookie value to get the session_key
- session_key = self.security.decode_guid( secure_id )
- try:
- # Make sure we have a valid UTF-8 string
- session_key = session_key.encode( 'utf8' )
- except UnicodeDecodeError:
- # We'll end up creating a new galaxy_session
- session_key = None
- if session_key:
- # Retrieve the galaxy_session id via the unique session_key
- galaxy_session = self.sa_session.query( self.app.model.GalaxySession ) \
- .filter( and_( self.app.model.GalaxySession.table.c.session_key==session_key, #noqa
- self.app.model.GalaxySession.table.c.is_valid==True ) ).first() #noqa
- # If remote user is in use it can invalidate the session and in some
- # cases won't have a cookie set above, so we need to to check some
- # things now.
- if self.app.config.use_remote_user:
- # If this is an api request, and they've passed a key, we let this go.
- assert self.app.config.remote_user_header in self.environ, \
- "use_remote_user is set but %s header was not provided" % self.app.config.remote_user_header
- remote_user_email = self.environ[ self.app.config.remote_user_header ]
- if getattr( self.app.config, "normalize_remote_user_email", False ):
- remote_user_email = remote_user_email.lower()
- if galaxy_session:
- # An existing session, make sure correct association exists
- if galaxy_session.user is None:
- # No user, associate
- galaxy_session.user = self.get_or_create_remote_user( remote_user_email )
- galaxy_session_requires_flush = True
- elif ((galaxy_session.user.email != remote_user_email) and
- ((not self.app.config.allow_user_impersonation) or
- (remote_user_email not in self.app.config.admin_users_list))):
- # Session exists but is not associated with the correct
- # remote user, and the currently set remote_user is not a
- # potentially impersonating admin.
- invalidate_existing_session = True
- user_for_new_session = self.get_or_create_remote_user( remote_user_email )
- log.warning( "User logged in as '%s' externally, but has a cookie as '%s' invalidating session",
- remote_user_email, galaxy_session.user.email )
- else:
- # No session exists, get/create user for new session
- user_for_new_session = self.get_or_create_remote_user( remote_user_email )
- else:
- if galaxy_session is not None and galaxy_session.user and galaxy_session.user.external:
- # Remote user support is not enabled, but there is an existing
- # session with an external user, invalidate
- invalidate_existing_session = True
- log.warning( "User '%s' is an external user with an existing session, invalidating session since external auth is disabled",
- galaxy_session.user.email )
- elif galaxy_session is not None and galaxy_session.user is not None and galaxy_session.user.deleted:
- invalidate_existing_session = True
- log.warning( "User '%s' is marked deleted, invalidating session" % galaxy_session.user.email )
- # Do we need to invalidate the session for some reason?
- if invalidate_existing_session:
- prev_galaxy_session = galaxy_session
- prev_galaxy_session.is_valid = False
- galaxy_session = None
- # No relevant cookies, or couldn't find, or invalid, so create a new session
- if galaxy_session is None:
- galaxy_session = self.__create_new_session( prev_galaxy_session, user_for_new_session )
- galaxy_session_requires_flush = True
- self.galaxy_session = galaxy_session
- self.__update_session_cookie( name=session_cookie )
- else:
- self.galaxy_session = galaxy_session
- # Do we need to flush the session?
- if galaxy_session_requires_flush:
- self.sa_session.add( galaxy_session )
- # FIXME: If prev_session is a proper relation this would not
- # be needed.
- if prev_galaxy_session:
- self.sa_session.add( prev_galaxy_session )
- self.sa_session.flush()
- # If the old session was invalid, get a new history with our new session
- if invalidate_existing_session:
- self.new_history()
-
- def _ensure_logged_in_user( self, environ, session_cookie ):
- # The value of session_cookie can be one of
- # 'galaxysession' or 'galaxycommunitysession'
- # Currently this method does nothing unless session_cookie is 'galaxysession'
- if session_cookie == 'galaxysession' and self.galaxy_session.user is None:
- # TODO: re-engineer to eliminate the use of allowed_paths
- # as maintenance overhead is far too high.
- allowed_paths = (
- url_for( controller='root', action='index' ),
- url_for( controller='root', action='tool_menu' ),
- url_for( controller='root', action='masthead' ),
- url_for( controller='root', action='history' ),
- url_for( controller='user', action='api_keys' ),
- url_for( controller='user', action='create' ),
- url_for( controller='user', action='index' ),
- url_for( controller='user', action='login' ),
- url_for( controller='user', action='logout' ),
- url_for( controller='user', action='manage_user_info' ),
- url_for( controller='user', action='set_default_permissions' ),
- url_for( controller='user', action='reset_password' ),
- url_for( controller='user', action='openid_auth' ),
- url_for( controller='user', action='openid_process' ),
- url_for( controller='user', action='openid_associate' ),
- url_for( controller='library', action='browse' ),
- url_for( controller='history', action='list' ),
- url_for( controller='dataset', action='list' )
- )
- display_as = url_for( controller='root', action='display_as' )
- if self.app.datatypes_registry.get_display_sites('ucsc') and self.request.path == display_as:
- try:
- host = socket.gethostbyaddr( self.environ[ 'REMOTE_ADDR' ] )[0]
- except( socket.error, socket.herror, socket.gaierror, socket.timeout ):
- host = None
- if host in UCSC_SERVERS:
- return
- external_display_path = url_for( controller='', action='display_application' )
- if self.request.path.startswith( external_display_path ):
- request_path_split = self.request.path.split( '/' )
- try:
- if (self.app.datatypes_registry.display_applications.get( request_path_split[-5] )
- and request_path_split[-4] in self.app.datatypes_registry.display_applications.get( request_path_split[-5] ).links
- and request_path_split[-3] != 'None'):
- return
- except IndexError:
- pass
- if self.request.path not in allowed_paths:
- self.response.send_redirect( url_for( controller='root', action='index' ) )
-
- def __create_new_session( self, prev_galaxy_session=None, user_for_new_session=None ):
- """
- Create a new GalaxySession for this request, possibly with a connection
- to a previous session (in `prev_galaxy_session`) and an existing user
- (in `user_for_new_session`).
-
- Caller is responsible for flushing the returned session.
- """
- session_key = self.security.get_new_guid()
- galaxy_session = self.app.model.GalaxySession(
- session_key=session_key,
- is_valid=True,
- remote_host=self.request.remote_host,
- remote_addr=self.request.remote_addr,
- referer=self.request.headers.get( 'Referer', None ) )
- if prev_galaxy_session:
- # Invalidated an existing session for some reason, keep track
- galaxy_session.prev_session_id = prev_galaxy_session.id
- if user_for_new_session:
- # The new session should be associated with the user
- galaxy_session.user = user_for_new_session
- return galaxy_session
-
- def get_or_create_remote_user( self, remote_user_email ):
- """
- Create a remote user with the email remote_user_email and return it
- """
- if not self.app.config.use_remote_user:
- return None
- if getattr( self.app.config, "normalize_remote_user_email", False ):
- remote_user_email = remote_user_email.lower()
- user = self.sa_session.query( self.app.model.User
- ).filter( self.app.model.User.table.c.email==remote_user_email ).first() #noqa
- if user:
- # GVK: June 29, 2009 - This is to correct the behavior of a previous bug where a private
- # role and default user / history permissions were not set for remote users. When a
- # remote user authenticates, we'll look for this information, and if missing, create it.
- if not self.app.security_agent.get_private_user_role( user ):
- self.app.security_agent.create_private_user_role( user )
- if 'webapp' not in self.environ or self.environ['webapp'] != 'tool_shed':
- if not user.default_permissions:
- self.app.security_agent.user_set_default_permissions( user )
- self.app.security_agent.user_set_default_permissions( user, history=True, dataset=True )
- elif user is None:
- username = remote_user_email.split( '@', 1 )[0].lower()
- random.seed()
- user = self.app.model.User( email=remote_user_email )
- user.set_password_cleartext( ''.join( random.sample( string.letters + string.digits, 12 ) ) )
- user.external = True
- # Replace invalid characters in the username
- for char in filter( lambda x: x not in string.ascii_lowercase + string.digits + '-', username ):
- username = username.replace( char, '-' )
- # Find a unique username - user can change it later
- if ( self.sa_session.query( self.app.model.User ).filter_by( username=username ).first() ):
- i = 1
- while ( self.sa_session.query( self.app.model.User ).filter_by( username=(username + '-' + str(i) ) ).first() ):
- i += 1
- username += '-' + str(i)
- user.username = username
- self.sa_session.add( user )
- self.sa_session.flush()
- self.app.security_agent.create_private_user_role( user )
- # We set default user permissions, before we log in and set the default history permissions
- if 'webapp' not in self.environ or self.environ['webapp'] != 'tool_shed':
- self.app.security_agent.user_set_default_permissions( user )
- # self.log_event( "Automatically created account '%s'", user.email )
- return user
-
- def __update_session_cookie( self, name='galaxysession' ):
- """
- Update the session cookie to match the current session.
- """
- self.set_cookie( self.security.encode_guid(self.galaxy_session.session_key ),
- name=name, path=self.app.config.cookie_path )
-
- def handle_user_login( self, user ):
- """
- Login a new user (possibly newly created)
-
- - create a new session
- - associate new session with user
- - if old session had a history and it was not associated with a user, associate it with the new session,
- otherwise associate the current session's history with the user
- - add the disk usage of the current session to the user's total disk usage
- """
- # Set the previous session
- prev_galaxy_session = self.galaxy_session
- prev_galaxy_session.is_valid = False
- # Define a new current_session
- self.galaxy_session = self.__create_new_session( prev_galaxy_session, user )
- if self.webapp.name == 'galaxy':
- cookie_name = 'galaxysession'
- # Associated the current user's last accessed history (if exists) with their new session
- history = None
- try:
- users_last_session = user.galaxy_sessions[0]
- last_accessed = True
- except:
- users_last_session = None
- last_accessed = False
- if (prev_galaxy_session.current_history and not
- prev_galaxy_session.current_history.deleted and
- prev_galaxy_session.current_history.datasets):
- if prev_galaxy_session.current_history.user is None or prev_galaxy_session.current_history.user == user:
- # If the previous galaxy session had a history, associate it with the new
- # session, but only if it didn't belong to a different user.
- history = prev_galaxy_session.current_history
- if prev_galaxy_session.user is None:
- # Increase the user's disk usage by the amount of the previous history's datasets if they didn't already own it.
- for hda in history.datasets:
- user.total_disk_usage += hda.quota_amount( user )
- elif self.galaxy_session.current_history:
- history = self.galaxy_session.current_history
- if (not history and users_last_session and
- users_last_session.current_history and not
- users_last_session.current_history.deleted):
- history = users_last_session.current_history
- elif not history:
- history = self.get_history( create=True )
- if history not in self.galaxy_session.histories:
- self.galaxy_session.add_history( history )
- if history.user is None:
- history.user = user
- self.galaxy_session.current_history = history
- if not last_accessed:
- # Only set default history permissions if current history is not from a previous session
- self.app.security_agent.history_set_default_permissions( history, dataset=True, bypass_manage_permission=True )
- self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session, history ) )
- else:
- cookie_name = 'galaxycommunitysession'
- self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session ) )
- self.sa_session.flush()
- # This method is not called from the Galaxy reports, so the cookie will always be galaxysession
- self.__update_session_cookie( name=cookie_name )
-
- def handle_user_logout( self, logout_all=False ):
- """
- Logout the current user:
- - invalidate the current session
- - create a new session with no user associated
- """
- prev_galaxy_session = self.galaxy_session
- prev_galaxy_session.is_valid = False
- self.galaxy_session = self.__create_new_session( prev_galaxy_session )
- self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session ) )
- galaxy_user_id = prev_galaxy_session.user_id
- if logout_all and galaxy_user_id is not None:
- for other_galaxy_session in self.sa_session.query( self.app.model.GalaxySession
- ).filter( and_( self.app.model.GalaxySession.table.c.user_id==galaxy_user_id, #noqa
- self.app.model.GalaxySession.table.c.is_valid==True, #noqa
- self.app.model.GalaxySession.table.c.id!=prev_galaxy_session.id ) ): #noqa
- other_galaxy_session.is_valid = False
- self.sa_session.add( other_galaxy_session )
- self.sa_session.flush()
- if self.webapp.name == 'galaxy':
- # This method is not called from the Galaxy reports, so the cookie will always be galaxysession
- self.__update_session_cookie( name='galaxysession' )
- elif self.webapp.name == 'tool_shed':
- self.__update_session_cookie( name='galaxycommunitysession' )
-
- def get_galaxy_session( self ):
- """
- Return the current galaxy session
- """
- return self.galaxy_session
-
- def get_history( self, create=False ):
- """
- Load the current history, creating a new one only if there is not
- current history and we're told to create.
- Transactions will not always have an active history (API requests), so
- None is a valid response.
- """
- history = None
- if self.galaxy_session:
- history = self.galaxy_session.current_history
- if not history and util.string_as_bool( create ):
- history = self.new_history()
- return history
-
- def set_history( self, history ):
- if history and not history.deleted:
- self.galaxy_session.current_history = history
- self.sa_session.add( self.galaxy_session )
- self.sa_session.flush()
-
- history = property( get_history, set_history )
-
- def get_or_create_default_history( self ):
- """
- Gets or creates a default history and associates it with the current
- session.
- """
-
- # There must be a user to fetch a default history.
- if not self.galaxy_session.user:
- return self.new_history()
-
- # Look for default history that (a) has default name + is not deleted and
- # (b) has no datasets. If suitable history found, use it; otherwise, create
- # new history.
- unnamed_histories = self.sa_session.query( self.app.model.History ).filter_by(
- user=self.galaxy_session.user,
- name=self.app.model.History.default_name,
- deleted=False )
- default_history = None
- for history in unnamed_histories:
- if len( history.datasets ) == 0:
- # Found suitable default history.
- default_history = history
- break
-
- # Set or create hsitory.
- if default_history:
- history = default_history
- self.set_history( history )
- else:
- history = self.new_history()
-
- return history
-
- def new_history( self, name=None ):
- """
- Create a new history and associate it with the current session and
- its associated user (if set).
- """
- # Create new history
- history = self.app.model.History()
- if name:
- history.name = name
- # Associate with session
- history.add_galaxy_session( self.galaxy_session )
- # Make it the session's current history
- self.galaxy_session.current_history = history
- # Associate with user
- if self.galaxy_session.user:
- history.user = self.galaxy_session.user
- # Track genome_build with history
- history.genome_build = self.app.genome_builds.default_value
- # Set the user's default history permissions
- self.app.security_agent.history_set_default_permissions( history )
- # Save
- self.sa_session.add_all( ( self.galaxy_session, history ) )
- self.sa_session.flush()
- return history
-
- @base.lazy_property
- def template_context( self ):
- return dict()
-
- def make_form_data( self, name, **kwargs ):
- rval = self.template_context[name] = FormData()
- rval.values.update( kwargs )
- return rval
-
- def set_message( self, message, type=None ):
- """
- Convenience method for setting the 'message' and 'message_type'
- element of the template context.
- """
- self.template_context['message'] = message
- if type:
- self.template_context['status'] = type
-
- def get_message( self ):
- """
- Convenience method for getting the 'message' element of the template
- context.
- """
- return self.template_context['message']
-
- def show_message( self, message, type='info', refresh_frames=[], cont=None, use_panels=False, active_view="" ):
- """
- Convenience method for displaying a simple page with a single message.
-
- `type`: one of "error", "warning", "info", or "done"; determines the
- type of dialog box and icon displayed with the message
-
- `refresh_frames`: names of frames in the interface that should be
- refreshed when the message is displayed
- """
- return self.fill_template( "message.mako", status=type, message=message, refresh_frames=refresh_frames, cont=cont, use_panels=use_panels, active_view=active_view )
-
- def show_error_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
- """
- Convenience method for displaying an error message. See `show_message`.
- """
- return self.show_message( message, 'error', refresh_frames, use_panels=use_panels, active_view=active_view )
-
- def show_ok_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
- """
- Convenience method for displaying an ok message. See `show_message`.
- """
- return self.show_message( message, 'done', refresh_frames, use_panels=use_panels, active_view=active_view )
-
- def show_warn_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
- """
- Convenience method for displaying an warn message. See `show_message`.
- """
- return self.show_message( message, 'warning', refresh_frames, use_panels=use_panels, active_view=active_view )
-
- def show_form( self, form, header=None, template="form.mako", use_panels=False, active_view="" ):
- """
- Convenience method for displaying a simple page with a single HTML
- form.
- """
- return self.fill_template( template, form=form, header=header,
- use_panels=( form.use_panels or use_panels ),
- active_view=active_view )
-
- def fill_template(self, filename, **kwargs):
- """
- Fill in a template, putting any keyword arguments on the context.
- """
- # call get_user so we can invalidate sessions from external users,
- # if external auth has been disabled.
- self.get_user()
- if filename.endswith( ".mako" ):
- return self.fill_template_mako( filename, **kwargs )
- else:
- template = Template( file=os.path.join(self.app.config.template_path, filename),
- searchList=[kwargs, self.template_context, dict(caller=self, t=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app)] )
- return str( template )
-
- def fill_template_mako( self, filename, template_lookup=None, **kwargs ):
- template_lookup = template_lookup or self.webapp.mako_template_lookup
- template = template_lookup.get_template( filename )
- template.output_encoding = 'utf-8'
-
- data = dict( caller=self, t=self, trans=self, h=helpers, util=util,
- request=self.request, response=self.response, app=self.app )
- data.update( self.template_context )
- data.update( kwargs )
- return template.render( **data )
-
- def stream_template_mako( self, filename, **kwargs ):
- template = self.webapp.mako_template_lookup.get_template( filename )
- template.output_encoding = 'utf-8'
- data = dict( caller=self, t=self, trans=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app )
- data.update( self.template_context )
- data.update( kwargs )
-
- def render( environ, start_response ):
- response_write = start_response( self.response.wsgi_status(), self.response.wsgi_headeritems() )
-
- class StreamBuffer( object ):
- def write( self, d ):
- response_write( d.encode( 'utf-8' ) )
- buffer = StreamBuffer()
- context = mako.runtime.Context( buffer, **data )
- template.render_context( context )
- return []
- return render
-
- def fill_template_string(self, template_string, context=None, **kwargs):
- """
- Fill in a template, putting any keyword arguments on the context.
- """
- template = Template( source=template_string,
- searchList=[context or kwargs, dict(caller=self)] )
- return str(template)
-
-
-class FormBuilder( object ):
- """
- Simple class describing an HTML form
- """
- def __init__( self, action="", title="", name="form", submit_text="submit", use_panels=False ):
- self.title = title
- self.name = name
- self.action = action
- self.submit_text = submit_text
- self.inputs = []
- self.use_panels = use_panels
-
- def add_input( self, type, name, label, value=None, error=None, help=None, use_label=True ):
- self.inputs.append( FormInput( type, label, name, value, error, help, use_label ) )
- return self
-
- def add_text( self, name, label, value=None, error=None, help=None ):
- return self.add_input( 'text', label, name, value, error, help )
-
- def add_password( self, name, label, value=None, error=None, help=None ):
- return self.add_input( 'password', label, name, value, error, help )
-
- def add_select( self, name, label, value=None, options=[], error=None, help=None, use_label=True ):
- self.inputs.append( SelectInput( name, label, value=value, options=options, error=error, help=help, use_label=use_label ) )
- return self
-
-
-class FormInput( object ):
- """
- Simple class describing a form input element
- """
- def __init__( self, type, name, label, value=None, error=None, help=None, use_label=True, extra_attributes={}, **kwargs ):
- self.type = type
- self.name = name
- self.label = label
- self.value = value
- self.error = error
- self.help = help
- self.use_label = use_label
- self.extra_attributes = extra_attributes
-
-
-class DatalistInput( FormInput ):
- """ Data list input """
-
- def __init__( self, name, *args, **kwargs ):
- if 'extra_attributes' not in kwargs:
- kwargs[ 'extra_attributes' ] = {}
- kwargs[ 'extra_attributes' ][ 'list' ] = name
- FormInput.__init__( self, None, name, *args, **kwargs )
- self.options = kwargs.get( 'options', {} )
-
- def body_html( self ):
- options = "".join( [ "<option value='%s'>%s</option>" % ( key, value ) for key, value in self.options.iteritems() ] )
- return """<datalist id="%s">%s</datalist>""" % ( self.name, options )
-
-
-class SelectInput( FormInput ):
- """ A select form input. """
- def __init__( self, name, label, value=None, options=[], error=None, help=None, use_label=True ):
- FormInput.__init__( self, "select", name, label, value=value, error=error, help=help, use_label=use_label )
- self.options = options
-
-
-class FormData( object ):
- """
- Class for passing data about a form to a template, very rudimentary, could
- be combined with the tool form handling to build something more general.
- """
- def __init__( self ):
- self.values = Bunch()
- self.errors = Bunch()
-
-
-class Bunch( dict ):
- """
- Bunch based on a dict
- """
- def __getattr__( self, key ):
- if key not in self:
- raise AttributeError(key)
- return self[key]
-
- def __setattr__( self, key, value ):
- self[key] = value
This diff is so big that we needed to truncate the remainder.
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
14 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/d57655849ac4/
Changeset: d57655849ac4
User: carlfeberhard
Date: 2014-09-03 14:57:25
Summary: Empty out web/framework/__init__ into decorator, transaction, webapp, and formbuilder, fix related imports
Affected #: 18 files
diff -r 012e6eb765684020eb597d8925e4ca5b58b9d904 -r d57655849ac41b2b1f90b73e2835d618df45e985 lib/galaxy/datatypes/metadata.py
--- a/lib/galaxy/datatypes/metadata.py
+++ b/lib/galaxy/datatypes/metadata.py
@@ -6,7 +6,6 @@
import copy
import cPickle
import json
-import logging
import os
import shutil
import sys
@@ -15,13 +14,19 @@
from os.path import abspath
-import galaxy.model
-from galaxy.util import listify, stringify_dictionary_keys, string_as_bool
-from galaxy.util.odict import odict
-from galaxy.util import in_directory
-from galaxy.web import form_builder
+from galaxy import eggs
+eggs.require( "SQLAlchemy >= 0.4" )
from sqlalchemy.orm import object_session
+import galaxy.model
+from galaxy.util import listify
+from galaxy.util import stringify_dictionary_keys
+from galaxy.util import string_as_bool
+from galaxy.util import in_directory
+from galaxy.util.odict import odict
+from galaxy.web import form_builder
+
+import logging
log = logging.getLogger(__name__)
STATEMENTS = "__galaxy_statements__" #this is the name of the property in a Datatype class where new metadata spec element Statements are stored
diff -r 012e6eb765684020eb597d8925e4ca5b58b9d904 -r d57655849ac41b2b1f90b73e2835d618df45e985 lib/galaxy/web/__init__.py
--- a/lib/galaxy/web/__init__.py
+++ b/lib/galaxy/web/__init__.py
@@ -1,23 +1,25 @@
"""
The Galaxy web application framework
"""
-from framework import expose
-from framework import json
-from framework import json_pretty
-from framework import require_login
-from framework import require_admin
from framework import url_for
-from framework import error
-from framework import form
-from framework import FormBuilder
-from framework import expose_api
-from framework import expose_api_anonymous
-from framework import expose_api_raw
-from framework import expose_api_raw_anonymous
-from framework.base import httpexceptions
+from framework.decorators import error
+from framework.decorators import expose
+from framework.decorators import json
+from framework.decorators import json_pretty
+from framework.decorators import require_login
+from framework.decorators import require_admin
+from framework.decorators import expose_api
+from framework.decorators import expose_api_anonymous
+from framework.decorators import expose_api_raw
+from framework.decorators import expose_api_raw_anonymous
# TODO: Drop and make these the default.
-from framework import _future_expose_api
-from framework import _future_expose_api_anonymous
-from framework import _future_expose_api_raw
-from framework import _future_expose_api_raw_anonymous
+from framework.decorators import _future_expose_api
+from framework.decorators import _future_expose_api_anonymous
+from framework.decorators import _future_expose_api_raw
+from framework.decorators import _future_expose_api_raw_anonymous
+
+from framework.formbuilder import form
+from framework.formbuilder import FormBuilder
+
+from framework.base import httpexceptions
diff -r 012e6eb765684020eb597d8925e4ca5b58b9d904 -r d57655849ac41b2b1f90b73e2835d618df45e985 lib/galaxy/web/base/pluginframework.py
--- a/lib/galaxy/web/base/pluginframework.py
+++ b/lib/galaxy/web/base/pluginframework.py
@@ -579,7 +579,7 @@
"""
Pass control over to trans and render ``template_filename``.
- :type trans: ``galaxy.web.framework.GalaxyWebTransaction``
+ :type trans: ``galaxy.web.framework.transaction.GalaxyWebTransaction``
:param trans: transaction doing the rendering
:type plugin: ``util.bunch.Bunch``
:param plugin: the plugin containing the template to render
diff -r 012e6eb765684020eb597d8925e4ca5b58b9d904 -r d57655849ac41b2b1f90b73e2835d618df45e985 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -2,1395 +2,9 @@
Galaxy web application framework
"""
-import hashlib
-import inspect
-import os
-import random
-import socket
-import string
-import time
-from traceback import format_exc
-from Cookie import CookieError
-from functools import wraps
-
from galaxy import eggs
-
-eggs.require( "Cheetah" )
-from Cheetah.Template import Template
-
-from galaxy import util
-from galaxy.exceptions import error_codes
-from galaxy.exceptions import MessageException
-from galaxy.util import asbool
-from galaxy.util import safe_str_cmp
-from galaxy.util.backports.importlib import import_module
-from galaxy.util.json import from_json_string, to_json_string
-from galaxy.util.sanitize_html import sanitize_html
-from galaxy.web.framework import base, helpers
-
-import paste.httpexceptions
-
-eggs.require( "Mako" )
-import mako.template
-import mako.lookup
-import mako.runtime
-
-eggs.require( "pytz" ) # Used by Babel.
-eggs.require( "Babel" )
-from babel.support import Translations
-from babel import Locale
-
-eggs.require( "SQLAlchemy >= 0.4" )
-from sqlalchemy import and_
-from sqlalchemy.orm.exc import NoResultFound
-
eggs.require( "pexpect" )
eggs.require( "amqp" )
-import logging
-log = logging.getLogger( __name__ )
-
+import base
url_for = base.routes.url_for
-
-UCSC_SERVERS = (
- 'hgw1.cse.ucsc.edu',
- 'hgw2.cse.ucsc.edu',
- 'hgw3.cse.ucsc.edu',
- 'hgw4.cse.ucsc.edu',
- 'hgw5.cse.ucsc.edu',
- 'hgw6.cse.ucsc.edu',
- 'hgw7.cse.ucsc.edu',
- 'hgw8.cse.ucsc.edu',
-)
-
-JSON_CONTENT_TYPE = "application/json"
-
-
-# ----------------------------------------------------------------------------- web controller decorators
-def expose( func ):
- """
- Decorator: mark a function as 'exposed' and thus web accessible
- """
- func.exposed = True
- return func
-
-def json( func, **json_kwargs ):
- """
- Format the response as JSON and set the response content type to
- JSON_CONTENT_TYPE.
- """
- @wraps(func)
- def call_and_format( self, trans, *args, **kwargs ):
- trans.response.set_content_type( JSON_CONTENT_TYPE )
- return to_json_string( func( self, trans, *args, **kwargs ), **json_kwargs )
- if not hasattr(func, '_orig'):
- call_and_format._orig = func
- call_and_format.exposed = True
- return call_and_format
-
-def json_pretty( func ):
- """
- Indent and sort returned JSON.
- """
- return json( func, indent=4, sort_keys=True )
-
-def require_login( verb="perform this action", use_panels=False, webapp='galaxy' ):
- def argcatcher( func ):
- @wraps(func)
- def decorator( self, trans, *args, **kwargs ):
- if trans.get_user():
- return func( self, trans, *args, **kwargs )
- else:
- return trans.show_error_message(
- 'You must be <a target="galaxy_main" href="%s">logged in</a> to %s.'
- % ( url_for( controller='user', action='login', webapp=webapp ), verb ), use_panels=use_panels )
- return decorator
- return argcatcher
-
-def require_admin( func ):
- @wraps(func)
- def decorator( self, trans, *args, **kwargs ):
- if not trans.user_is_admin():
- msg = "You must be an administrator to access this feature."
- user = trans.get_user()
- if not trans.app.config.admin_users_list:
- msg = "You must be logged in as an administrator to access this feature, but no administrators are set in the Galaxy configuration."
- elif not user:
- msg = "You must be logged in as an administrator to access this feature."
- trans.response.status = 403
- if trans.response.get_content_type() == 'application/json':
- return msg
- else:
- return trans.show_error_message( msg )
- return func( self, trans, *args, **kwargs )
- return decorator
-
-
-# ----------------------------------------------------------------------------- (original) api decorators
-def expose_api( func, to_json=True, user_required=True ):
- """
- Expose this function via the API.
- """
- @wraps(func)
- def decorator( self, trans, *args, **kwargs ):
- def error( environ, start_response ):
- start_response( error_status, [('Content-type', 'text/plain')] )
- return error_message
- error_status = '403 Forbidden'
- if trans.error_message:
- return trans.error_message
- if user_required and trans.anonymous:
- error_message = "API Authentication Required for this request"
- return error
- if trans.request.body:
- try:
- kwargs['payload'] = __extract_payload_from_request(trans, func, kwargs)
- except ValueError:
- error_status = '400 Bad Request'
- error_message = 'Your request did not appear to be valid JSON, please consult the API documentation'
- return error
- trans.response.set_content_type( "application/json" )
- # send 'do not cache' headers to handle IE's caching of ajax get responses
- trans.response.headers[ 'Cache-Control' ] = "max-age=0,no-cache,no-store"
- # Perform api_run_as processing, possibly changing identity
- if 'payload' in kwargs and 'run_as' in kwargs['payload']:
- if not trans.user_can_do_run_as():
- error_message = 'User does not have permissions to run jobs as another user'
- return error
- try:
- decoded_user_id = trans.security.decode_id( kwargs['payload']['run_as'] )
- except TypeError:
- trans.response.status = 400
- return "Malformed user id ( %s ) specified, unable to decode." % str( kwargs['payload']['run_as'] )
- try:
- user = trans.sa_session.query( trans.app.model.User ).get( decoded_user_id )
- trans.api_inherit_admin = trans.user_is_admin()
- trans.set_user(user)
- except:
- trans.response.status = 400
- return "That user does not exist."
- try:
- rval = func( self, trans, *args, **kwargs)
- if to_json and trans.debug:
- rval = to_json_string( rval, indent=4, sort_keys=True )
- elif to_json:
- rval = to_json_string( rval )
- return rval
- except paste.httpexceptions.HTTPException:
- raise # handled
- except:
- log.exception( 'Uncaught exception in exposed API method:' )
- raise paste.httpexceptions.HTTPServerError()
- if not hasattr(func, '_orig'):
- decorator._orig = func
- decorator.exposed = True
- return decorator
-
-def __extract_payload_from_request(trans, func, kwargs):
- content_type = trans.request.headers['content-type']
- if content_type.startswith('application/x-www-form-urlencoded') or content_type.startswith('multipart/form-data'):
- # If the content type is a standard type such as multipart/form-data, the wsgi framework parses the request body
- # and loads all field values into kwargs. However, kwargs also contains formal method parameters etc. which
- # are not a part of the request body. This is a problem because it's not possible to differentiate between values
- # which are a part of the request body, and therefore should be a part of the payload, and values which should not be
- # in the payload. Therefore, the decorated method's formal arguments are discovered through reflection and removed from
- # the payload dictionary. This helps to prevent duplicate argument conflicts in downstream methods.
- payload = kwargs.copy()
- named_args, _, _, _ = inspect.getargspec(func)
- for arg in named_args:
- payload.pop(arg, None)
- for k, v in payload.iteritems():
- if isinstance(v, (str, unicode)):
- try:
- payload[k] = from_json_string(v)
- except:
- # may not actually be json, just continue
- pass
- payload = util.recursively_stringify_dictionary_keys( payload )
- else:
- # Assume application/json content type and parse request body manually, since wsgi won't do it. However, the order of this check
- # should ideally be in reverse, with the if clause being a check for application/json and the else clause assuming a standard encoding
- # such as multipart/form-data. Leaving it as is for backward compatibility, just in case.
- payload = util.recursively_stringify_dictionary_keys( from_json_string( trans.request.body ) )
- return payload
-
-def expose_api_raw( func ):
- """
- Expose this function via the API but don't dump the results
- to JSON.
- """
- return expose_api( func, to_json=False )
-
-def expose_api_raw_anonymous( func ):
- """
- Expose this function via the API but don't dump the results
- to JSON.
- """
- return expose_api( func, to_json=False, user_required=False )
-
-def expose_api_anonymous( func, to_json=True ):
- """
- Expose this function via the API but don't require a set user.
- """
- return expose_api( func, to_json=to_json, user_required=False )
-
-
-# ----------------------------------------------------------------------------- (new) api decorators
-# TODO: rename as expose_api and make default.
-def _future_expose_api( func, to_json=True, user_required=True ):
- """
- Expose this function via the API.
- """
- @wraps(func)
- def decorator( self, trans, *args, **kwargs ):
- if trans.error_message:
- # TODO: Document this branch, when can this happen,
- # I don't understand it.
- return __api_error_response( trans, err_msg=trans.error_message )
- if user_required and trans.anonymous:
- error_code = error_codes.USER_NO_API_KEY
- # Use error codes default error message.
- err_msg = "API authentication required for this request"
- return __api_error_response( trans, err_code=error_code, err_msg=err_msg, status_code=403 )
- if trans.request.body:
- try:
- kwargs['payload'] = __extract_payload_from_request(trans, func, kwargs)
- except ValueError:
- error_code = error_codes.USER_INVALID_JSON
- return __api_error_response( trans, status_code=400, err_code=error_code )
-
- trans.response.set_content_type( JSON_CONTENT_TYPE )
- # send 'do not cache' headers to handle IE's caching of ajax get responses
- trans.response.headers[ 'Cache-Control' ] = "max-age=0,no-cache,no-store"
- # TODO: Refactor next block out into a helper procedure.
- # Perform api_run_as processing, possibly changing identity
- if 'payload' in kwargs and 'run_as' in kwargs['payload']:
- if not trans.user_can_do_run_as():
- error_code = error_codes.USER_CANNOT_RUN_AS
- return __api_error_response( trans, err_code=error_code, status_code=403 )
- try:
- decoded_user_id = trans.security.decode_id( kwargs['payload']['run_as'] )
- except TypeError:
- error_message = "Malformed user id ( %s ) specified, unable to decode." % str( kwargs['payload']['run_as'] )
- error_code = error_codes.USER_INVALID_RUN_AS
- return __api_error_response( trans, err_code=error_code, err_msg=error_message, status_code=400)
- try:
- user = trans.sa_session.query( trans.app.model.User ).get( decoded_user_id )
- trans.api_inherit_admin = trans.user_is_admin()
- trans.set_user(user)
- except:
- error_code = error_codes.USER_INVALID_RUN_AS
- return __api_error_response( trans, err_code=error_code, status_code=400 )
- try:
- rval = func( self, trans, *args, **kwargs)
- if to_json and trans.debug:
- rval = to_json_string( rval, indent=4, sort_keys=True )
- elif to_json:
- rval = to_json_string( rval )
- return rval
- except MessageException as e:
- traceback_string = format_exc()
- return __api_error_response( trans, exception=e, traceback=traceback_string )
- except paste.httpexceptions.HTTPException:
- # TODO: Allow to pass or format for the API???
- raise # handled
- except Exception as e:
- traceback_string = format_exc()
- error_message = 'Uncaught exception in exposed API method:'
- log.exception( error_message )
- return __api_error_response(
- trans,
- status_code=500,
- exception=e,
- traceback=traceback_string,
- err_msg=error_message,
- err_code=error_codes.UNKNOWN
- )
- if not hasattr(func, '_orig'):
- decorator._orig = func
- decorator.exposed = True
- return decorator
-
-def __api_error_message( trans, **kwds ):
- exception = kwds.get( "exception", None )
- if exception:
- # If we are passed a MessageException use err_msg.
- default_error_code = getattr( exception, "err_code", error_codes.UNKNOWN )
- default_error_message = getattr( exception, "err_msg", default_error_code.default_error_message )
- extra_error_info = getattr( exception, 'extra_error_info', {} )
- if not isinstance( extra_error_info, dict ):
- extra_error_info = {}
- else:
- default_error_message = "Error processing API request."
- default_error_code = error_codes.UNKNOWN
- extra_error_info = {}
- traceback_string = kwds.get( "traceback", "No traceback available." )
- err_msg = kwds.get( "err_msg", default_error_message )
- error_code_object = kwds.get( "err_code", default_error_code )
- try:
- error_code = error_code_object.code
- except AttributeError:
- # Some sort of bad error code sent in, logic failure on part of
- # Galaxy developer.
- error_code = error_codes.UNKNOWN.code
- # Would prefer the terminology of error_code and error_message, but
- # err_msg used a good number of places already. Might as well not change
- # it?
- error_response = dict( err_msg=err_msg, err_code=error_code, **extra_error_info )
- if trans.debug: # TODO: Should admins get to see traceback as well?
- error_response[ "traceback" ] = traceback_string
- return error_response
-
-def __api_error_response( trans, **kwds ):
- error_dict = __api_error_message( trans, **kwds )
- exception = kwds.get( "exception", None )
- # If we are given an status code directly - use it - otherwise check
- # the exception for a status_code attribute.
- if "status_code" in kwds:
- status_code = int( kwds.get( "status_code" ) )
- elif hasattr( exception, "status_code" ):
- status_code = int( exception.status_code )
- else:
- status_code = 500
- response = trans.response
- if not response.status or str(response.status).startswith("20"):
- # Unset status code appears to be string '200 OK', if anything
- # non-success (i.e. not 200 or 201) has been set, do not override
- # underlying controller.
- response.status = status_code
- return to_json_string( error_dict )
-
-
-# TODO: rename as expose_api and make default.
-def _future_expose_api_anonymous( func, to_json=True ):
- """
- Expose this function via the API but don't require a set user.
- """
- return _future_expose_api( func, to_json=to_json, user_required=False )
-
-
-def _future_expose_api_raw( func ):
- return _future_expose_api( func, to_json=False, user_required=True )
-
-
-def _future_expose_api_raw_anonymous( func ):
- return _future_expose_api( func, to_json=False, user_required=False )
-
-
-
-NOT_SET = object()
-
-
-def error( message ):
- raise MessageException( message, type='error' )
-
-
-def form( *args, **kwargs ):
- return FormBuilder( *args, **kwargs )
-
-
-class WebApplication( base.WebApplication ):
-
- def __init__( self, galaxy_app, session_cookie='galaxysession', name=None ):
- self.name = name
- base.WebApplication.__init__( self )
- self.set_transaction_factory( lambda e: self.transaction_chooser( e, galaxy_app, session_cookie ) )
- # Mako support
- self.mako_template_lookup = self.create_mako_template_lookup( galaxy_app, name )
- # Security helper
- self.security = galaxy_app.security
-
- def create_mako_template_lookup( self, galaxy_app, name ):
- paths = []
- # First look in webapp specific directory
- if name is not None:
- paths.append( os.path.join( galaxy_app.config.template_path, 'webapps', name ) )
- # Then look in root directory
- paths.append( galaxy_app.config.template_path )
- # Create TemplateLookup with a small cache
- return mako.lookup.TemplateLookup(directories=paths,
- module_directory=galaxy_app.config.template_cache,
- collection_size=500,
- output_encoding='utf-8' )
-
- def handle_controller_exception( self, e, trans, **kwargs ):
- if isinstance( e, MessageException ):
- # In the case of a controller exception, sanitize to make sure
- # unsafe html input isn't reflected back to the user
- return trans.show_message( sanitize_html(e.err_msg), e.type )
-
- def make_body_iterable( self, trans, body ):
- if isinstance( body, FormBuilder ):
- body = trans.show_form( body )
- return base.WebApplication.make_body_iterable( self, trans, body )
-
- def transaction_chooser( self, environ, galaxy_app, session_cookie ):
- return GalaxyWebTransaction( environ, galaxy_app, self, session_cookie )
-
- def add_ui_controllers( self, package_name, app ):
- """
- Search for UI controllers in `package_name` and add
- them to the webapp.
- """
- from galaxy.web.base.controller import BaseUIController
- from galaxy.web.base.controller import ControllerUnavailable
- package = import_module( package_name )
- controller_dir = package.__path__[0]
- for fname in os.listdir( controller_dir ):
- if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
- name = fname[:-3]
- module_name = package_name + "." + name
- try:
- module = import_module( module_name )
- except ControllerUnavailable, exc:
- log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
- continue
- # Look for a controller inside the modules
- for key in dir( module ):
- T = getattr( module, key )
- if inspect.isclass( T ) and T is not BaseUIController and issubclass( T, BaseUIController ):
- controller = self._instantiate_controller( T, app )
- self.add_ui_controller( name, controller )
-
- def add_api_controllers( self, package_name, app ):
- """
- Search for UI controllers in `package_name` and add
- them to the webapp.
- """
- from galaxy.web.base.controller import BaseAPIController
- from galaxy.web.base.controller import ControllerUnavailable
- package = import_module( package_name )
- controller_dir = package.__path__[0]
- for fname in os.listdir( controller_dir ):
- if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
- name = fname[:-3]
- module_name = package_name + "." + name
- try:
- module = import_module( module_name )
- except ControllerUnavailable, exc:
- log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
- continue
- for key in dir( module ):
- T = getattr( module, key )
- # Exclude classes such as BaseAPIController and BaseTagItemsController
- if inspect.isclass( T ) and not key.startswith("Base") and issubclass( T, BaseAPIController ):
- # By default use module_name, but allow controller to override name
- controller_name = getattr( T, "controller_name", name )
- controller = self._instantiate_controller( T, app )
- self.add_api_controller( controller_name, controller )
-
- def _instantiate_controller( self, T, app ):
- """ Extension point, allow apps to contstruct controllers differently,
- really just used to stub out actual controllers for routes testing.
- """
- return T( app )
-
-
-class ProvidesAppContext( object ):
- """ For transaction-like objects to provide Galaxy convience layer for
- database and event handling.
-
- Mixed in class must provide `app` property.
- """
-
- def log_action( self, user=None, action=None, context=None, params=None):
- """
- Application-level logging of user actions.
- """
- if self.app.config.log_actions:
- action = self.app.model.UserAction(action=action, context=context, params=unicode( to_json_string( params ) ) )
- try:
- if user:
- action.user = user
- else:
- action.user = self.user
- except:
- action.user = None
- try:
- action.session_id = self.galaxy_session.id
- except:
- action.session_id = None
- self.sa_session.add( action )
- self.sa_session.flush()
-
- def log_event( self, message, tool_id=None, **kwargs ):
- """
- Application level logging. Still needs fleshing out (log levels and such)
- Logging events is a config setting - if False, do not log.
- """
- if self.app.config.log_events:
- event = self.app.model.Event()
- event.tool_id = tool_id
- try:
- event.message = message % kwargs
- except:
- event.message = message
- try:
- event.history = self.get_history()
- except:
- event.history = None
- try:
- event.history_id = self.history.id
- except:
- event.history_id = None
- try:
- event.user = self.user
- except:
- event.user = None
- try:
- event.session_id = self.galaxy_session.id
- except:
- event.session_id = None
- self.sa_session.add( event )
- self.sa_session.flush()
-
- @property
- def sa_session( self ):
- """
- Returns a SQLAlchemy session -- currently just gets the current
- session from the threadlocal session context, but this is provided
- to allow migration toward a more SQLAlchemy 0.4 style of use.
- """
- return self.app.model.context.current
-
- def expunge_all( self ):
- app = self.app
- context = app.model.context
- context.expunge_all()
- # This is a bit hacky, should refctor this. Maybe refactor to app -> expunge_all()
- if hasattr(app, 'install_model'):
- install_model = app.install_model
- if install_model != app.model:
- install_model.context.expunge_all()
-
- def get_toolbox(self):
- """Returns the application toolbox"""
- return self.app.toolbox
-
- @property
- def model( self ):
- return self.app.model
-
- @property
- def install_model( self ):
- return self.app.install_model
-
- def request_types(self):
- if self.sa_session.query( self.app.model.RequestType ).filter_by( deleted=False ).count() > 0:
- return True
- return False
-
-
-class ProvidesUserContext( object ):
- """ For transaction-like objects to provide Galaxy convience layer for
- reasoning about users.
-
- Mixed in class must provide `user`, `api_inherit_admin`, and `app`
- properties.
- """
-
- @property
- def anonymous( self ):
- return self.user is None and not self.api_inherit_admin
-
- def get_current_user_roles( self ):
- user = self.user
- if user:
- roles = user.all_roles()
- else:
- roles = []
- return roles
-
- def user_is_admin( self ):
- if self.api_inherit_admin:
- return True
- return self.user and self.user.email in self.app.config.admin_users_list
-
- def user_can_do_run_as( self ):
- run_as_users = [ user for user in self.app.config.get( "api_allow_run_as", "" ).split( "," ) if user ]
- if not run_as_users:
- return False
- user_in_run_as_users = self.user and self.user.email in run_as_users
- # Can do if explicitly in list or master_api_key supplied.
- can_do_run_as = user_in_run_as_users or self.api_inherit_admin
- return can_do_run_as
-
- @property
- def user_ftp_dir( self ):
- identifier = self.app.config.ftp_upload_dir_identifier
- return os.path.join( self.app.config.ftp_upload_dir, getattr(self.user, identifier) )
-
-
-class ProvidesHistoryContext( object ):
- """ For transaction-like objects to provide Galaxy convience layer for
- reasoning about histories.
-
- Mixed in class must provide `user`, `history`, and `app`
- properties.
- """
-
- def db_dataset_for( self, dbkey ):
- """
- Returns the db_file dataset associated/needed by `dataset`, or `None`.
- """
- # If no history, return None.
- if self.history is None:
- return None
- if isinstance(self.history, Bunch):
- # The API presents a Bunch for a history. Until the API is
- # more fully featured for handling this, also return None.
- return None
- datasets = self.sa_session.query( self.app.model.HistoryDatasetAssociation ) \
- .filter_by( deleted=False, history_id=self.history.id, extension="len" )
- for ds in datasets:
- if dbkey == ds.dbkey:
- return ds
- return None
-
- @property
- def db_builds( self ):
- """
- Returns the builds defined by galaxy and the builds defined by
- the user (chromInfo in history).
- """
- # FIXME: This method should be removed
- return self.app.genome_builds.get_genome_build_names( trans=self )
-
-
-class GalaxyWebTransaction( base.DefaultWebTransaction, ProvidesAppContext, ProvidesUserContext, ProvidesHistoryContext ):
- """
- Encapsulates web transaction specific state for the Galaxy application
- (specifically the user's "cookie" session and history)
- """
-
- def __init__( self, environ, app, webapp, session_cookie=None):
- self.app = app
- self.webapp = webapp
- self.security = webapp.security
- base.DefaultWebTransaction.__init__( self, environ )
- self.setup_i18n()
- self.expunge_all()
- self.debug = asbool( self.app.config.get( 'debug', False ) )
- # Flag indicating whether we are in workflow building mode (means
- # that the current history should not be used for parameter values
- # and such).
- self.workflow_building_mode = False
- # Flag indicating whether this is an API call and the API key user is an administrator
- self.api_inherit_admin = False
- self.__user = None
- self.galaxy_session = None
- self.error_message = None
-
- if self.environ.get('is_api_request', False):
- # With API requests, if there's a key, use it and associate the
- # user with the transaction.
- # If not, check for an active session but do not create one.
- # If an error message is set here, it's sent back using
- # trans.show_error in the response -- in expose_api.
- self.error_message = self._authenticate_api( session_cookie )
- elif self.app.name == "reports":
- self.galaxy_session = None
- else:
- # This is a web request, get or create session.
- self._ensure_valid_session( session_cookie )
- if self.galaxy_session:
- # When we've authenticated by session, we have to check the
- # following.
- # Prevent deleted users from accessing Galaxy
- if self.app.config.use_remote_user and self.galaxy_session.user.deleted:
- self.response.send_redirect( url_for( '/static/user_disabled.html' ) )
- if self.app.config.require_login:
- self._ensure_logged_in_user( environ, session_cookie )
-
- def setup_i18n( self ):
- locales = []
- if 'HTTP_ACCEPT_LANGUAGE' in self.environ:
- # locales looks something like: ['en', 'en-us;q=0.7', 'ja;q=0.3']
- client_locales = self.environ['HTTP_ACCEPT_LANGUAGE'].split( ',' )
- for locale in client_locales:
- try:
- locales.append( Locale.parse( locale.split( ';' )[0].strip(), sep='-' ).language )
- except Exception, e:
- log.debug( "Error parsing locale '%s'. %s: %s", locale, type( e ), e )
- if not locales:
- # Default to English
- locales = 'en'
- t = Translations.load( dirname='locale', locales=locales, domain='ginga' )
- self.template_context.update( dict( _=t.ugettext, n_=t.ugettext, N_=t.ungettext ) )
-
- def get_user( self ):
- """Return the current user if logged in or None."""
- if self.galaxy_session:
- return self.galaxy_session.user
- else:
- return self.__user
-
- def set_user( self, user ):
- """Set the current user."""
- if self.galaxy_session:
- self.galaxy_session.user = user
- self.sa_session.add( self.galaxy_session )
- self.sa_session.flush()
- self.__user = user
-
- user = property( get_user, set_user )
-
- def get_cookie( self, name='galaxysession' ):
- """Convenience method for getting a session cookie"""
- try:
- # If we've changed the cookie during the request return the new value
- if name in self.response.cookies:
- return self.response.cookies[name].value
- else:
- return self.request.cookies[name].value
- except:
- return None
-
- def set_cookie( self, value, name='galaxysession', path='/', age=90, version='1' ):
- """Convenience method for setting a session cookie"""
- # The galaxysession cookie value must be a high entropy 128 bit random number encrypted
- # using a server secret key. Any other value is invalid and could pose security issues.
- self.response.cookies[name] = value
- self.response.cookies[name]['path'] = path
- self.response.cookies[name]['max-age'] = 3600 * 24 * age # 90 days
- tstamp = time.localtime( time.time() + 3600 * 24 * age )
- self.response.cookies[name]['expires'] = time.strftime( '%a, %d-%b-%Y %H:%M:%S GMT', tstamp )
- self.response.cookies[name]['version'] = version
- try:
- self.response.cookies[name]['httponly'] = True
- except CookieError, e:
- log.warning( "Error setting httponly attribute in cookie '%s': %s" % ( name, e ) )
-
- def _authenticate_api( self, session_cookie ):
- """
- Authenticate for the API via key or session (if available).
- """
- api_key = self.request.params.get('key', None)
- secure_id = self.get_cookie( name=session_cookie )
- api_key_supplied = self.environ.get('is_api_request', False) and api_key
- if api_key_supplied and self._check_master_api_key( api_key ):
- self.api_inherit_admin = True
- log.info( "Session authenticated using Galaxy master api key" )
- self.user = None
- self.galaxy_session = None
- elif api_key_supplied:
- # Sessionless API transaction, we just need to associate a user.
- try:
- provided_key = self.sa_session.query( self.app.model.APIKeys ).filter( self.app.model.APIKeys.table.c.key == api_key ).one()
- except NoResultFound:
- return 'Provided API key is not valid.'
- if provided_key.user.deleted:
- return 'User account is deactivated, please contact an administrator.'
- newest_key = provided_key.user.api_keys[0]
- if newest_key.key != provided_key.key:
- return 'Provided API key has expired.'
- self.set_user( provided_key.user )
- elif secure_id:
- # API authentication via active session
- # Associate user using existing session
- self._ensure_valid_session( session_cookie )
- else:
- # Anonymous API interaction -- anything but @expose_api_anonymous will fail past here.
- self.user = None
- self.galaxy_session = None
-
- def _check_master_api_key( self, api_key ):
- master_api_key = getattr( self.app.config, 'master_api_key', None )
- if not master_api_key:
- return False
- # Hash keys to make them the same size, so we can do safe comparison.
- master_hash = hashlib.sha256( master_api_key ).hexdigest()
- provided_hash = hashlib.sha256( api_key ).hexdigest()
- return safe_str_cmp( master_hash, provided_hash )
-
- def _ensure_valid_session( self, session_cookie, create=True):
- """
- Ensure that a valid Galaxy session exists and is available as
- trans.session (part of initialization)
-
- Support for universe_session and universe_user cookies has been
- removed as of 31 Oct 2008.
- """
- # Try to load an existing session
- secure_id = self.get_cookie( name=session_cookie )
- galaxy_session = None
- prev_galaxy_session = None
- user_for_new_session = None
- invalidate_existing_session = False
- # Track whether the session has changed so we can avoid calling flush
- # in the most common case (session exists and is valid).
- galaxy_session_requires_flush = False
- if secure_id:
- # Decode the cookie value to get the session_key
- session_key = self.security.decode_guid( secure_id )
- try:
- # Make sure we have a valid UTF-8 string
- session_key = session_key.encode( 'utf8' )
- except UnicodeDecodeError:
- # We'll end up creating a new galaxy_session
- session_key = None
- if session_key:
- # Retrieve the galaxy_session id via the unique session_key
- galaxy_session = self.sa_session.query( self.app.model.GalaxySession ) \
- .filter( and_( self.app.model.GalaxySession.table.c.session_key==session_key, #noqa
- self.app.model.GalaxySession.table.c.is_valid==True ) ).first() #noqa
- # If remote user is in use it can invalidate the session and in some
- # cases won't have a cookie set above, so we need to to check some
- # things now.
- if self.app.config.use_remote_user:
- # If this is an api request, and they've passed a key, we let this go.
- assert self.app.config.remote_user_header in self.environ, \
- "use_remote_user is set but %s header was not provided" % self.app.config.remote_user_header
- remote_user_email = self.environ[ self.app.config.remote_user_header ]
- if getattr( self.app.config, "normalize_remote_user_email", False ):
- remote_user_email = remote_user_email.lower()
- if galaxy_session:
- # An existing session, make sure correct association exists
- if galaxy_session.user is None:
- # No user, associate
- galaxy_session.user = self.get_or_create_remote_user( remote_user_email )
- galaxy_session_requires_flush = True
- elif ((galaxy_session.user.email != remote_user_email) and
- ((not self.app.config.allow_user_impersonation) or
- (remote_user_email not in self.app.config.admin_users_list))):
- # Session exists but is not associated with the correct
- # remote user, and the currently set remote_user is not a
- # potentially impersonating admin.
- invalidate_existing_session = True
- user_for_new_session = self.get_or_create_remote_user( remote_user_email )
- log.warning( "User logged in as '%s' externally, but has a cookie as '%s' invalidating session",
- remote_user_email, galaxy_session.user.email )
- else:
- # No session exists, get/create user for new session
- user_for_new_session = self.get_or_create_remote_user( remote_user_email )
- else:
- if galaxy_session is not None and galaxy_session.user and galaxy_session.user.external:
- # Remote user support is not enabled, but there is an existing
- # session with an external user, invalidate
- invalidate_existing_session = True
- log.warning( "User '%s' is an external user with an existing session, invalidating session since external auth is disabled",
- galaxy_session.user.email )
- elif galaxy_session is not None and galaxy_session.user is not None and galaxy_session.user.deleted:
- invalidate_existing_session = True
- log.warning( "User '%s' is marked deleted, invalidating session" % galaxy_session.user.email )
- # Do we need to invalidate the session for some reason?
- if invalidate_existing_session:
- prev_galaxy_session = galaxy_session
- prev_galaxy_session.is_valid = False
- galaxy_session = None
- # No relevant cookies, or couldn't find, or invalid, so create a new session
- if galaxy_session is None:
- galaxy_session = self.__create_new_session( prev_galaxy_session, user_for_new_session )
- galaxy_session_requires_flush = True
- self.galaxy_session = galaxy_session
- self.__update_session_cookie( name=session_cookie )
- else:
- self.galaxy_session = galaxy_session
- # Do we need to flush the session?
- if galaxy_session_requires_flush:
- self.sa_session.add( galaxy_session )
- # FIXME: If prev_session is a proper relation this would not
- # be needed.
- if prev_galaxy_session:
- self.sa_session.add( prev_galaxy_session )
- self.sa_session.flush()
- # If the old session was invalid, get a new history with our new session
- if invalidate_existing_session:
- self.new_history()
-
- def _ensure_logged_in_user( self, environ, session_cookie ):
- # The value of session_cookie can be one of
- # 'galaxysession' or 'galaxycommunitysession'
- # Currently this method does nothing unless session_cookie is 'galaxysession'
- if session_cookie == 'galaxysession' and self.galaxy_session.user is None:
- # TODO: re-engineer to eliminate the use of allowed_paths
- # as maintenance overhead is far too high.
- allowed_paths = (
- url_for( controller='root', action='index' ),
- url_for( controller='root', action='tool_menu' ),
- url_for( controller='root', action='masthead' ),
- url_for( controller='root', action='history' ),
- url_for( controller='user', action='api_keys' ),
- url_for( controller='user', action='create' ),
- url_for( controller='user', action='index' ),
- url_for( controller='user', action='login' ),
- url_for( controller='user', action='logout' ),
- url_for( controller='user', action='manage_user_info' ),
- url_for( controller='user', action='set_default_permissions' ),
- url_for( controller='user', action='reset_password' ),
- url_for( controller='user', action='openid_auth' ),
- url_for( controller='user', action='openid_process' ),
- url_for( controller='user', action='openid_associate' ),
- url_for( controller='library', action='browse' ),
- url_for( controller='history', action='list' ),
- url_for( controller='dataset', action='list' )
- )
- display_as = url_for( controller='root', action='display_as' )
- if self.app.config.ucsc_display_sites and self.request.path == display_as:
- try:
- host = socket.gethostbyaddr( self.environ[ 'REMOTE_ADDR' ] )[0]
- except( socket.error, socket.herror, socket.gaierror, socket.timeout ):
- host = None
- if host in UCSC_SERVERS:
- return
- external_display_path = url_for( controller='', action='display_application' )
- if self.request.path.startswith( external_display_path ):
- request_path_split = self.request.path.split( '/' )
- try:
- if (self.app.datatypes_registry.display_applications.get( request_path_split[-5] )
- and request_path_split[-4] in self.app.datatypes_registry.display_applications.get( request_path_split[-5] ).links
- and request_path_split[-3] != 'None'):
- return
- except IndexError:
- pass
- if self.request.path not in allowed_paths:
- self.response.send_redirect( url_for( controller='root', action='index' ) )
-
- def __create_new_session( self, prev_galaxy_session=None, user_for_new_session=None ):
- """
- Create a new GalaxySession for this request, possibly with a connection
- to a previous session (in `prev_galaxy_session`) and an existing user
- (in `user_for_new_session`).
-
- Caller is responsible for flushing the returned session.
- """
- session_key = self.security.get_new_guid()
- galaxy_session = self.app.model.GalaxySession(
- session_key=session_key,
- is_valid=True,
- remote_host=self.request.remote_host,
- remote_addr=self.request.remote_addr,
- referer=self.request.headers.get( 'Referer', None ) )
- if prev_galaxy_session:
- # Invalidated an existing session for some reason, keep track
- galaxy_session.prev_session_id = prev_galaxy_session.id
- if user_for_new_session:
- # The new session should be associated with the user
- galaxy_session.user = user_for_new_session
- return galaxy_session
-
- def get_or_create_remote_user( self, remote_user_email ):
- """
- Create a remote user with the email remote_user_email and return it
- """
- if not self.app.config.use_remote_user:
- return None
- if getattr( self.app.config, "normalize_remote_user_email", False ):
- remote_user_email = remote_user_email.lower()
- user = self.sa_session.query( self.app.model.User
- ).filter( self.app.model.User.table.c.email==remote_user_email ).first() #noqa
- if user:
- # GVK: June 29, 2009 - This is to correct the behavior of a previous bug where a private
- # role and default user / history permissions were not set for remote users. When a
- # remote user authenticates, we'll look for this information, and if missing, create it.
- if not self.app.security_agent.get_private_user_role( user ):
- self.app.security_agent.create_private_user_role( user )
- if 'webapp' not in self.environ or self.environ['webapp'] != 'tool_shed':
- if not user.default_permissions:
- self.app.security_agent.user_set_default_permissions( user )
- self.app.security_agent.user_set_default_permissions( user, history=True, dataset=True )
- elif user is None:
- username = remote_user_email.split( '@', 1 )[0].lower()
- random.seed()
- user = self.app.model.User( email=remote_user_email )
- user.set_password_cleartext( ''.join( random.sample( string.letters + string.digits, 12 ) ) )
- user.external = True
- # Replace invalid characters in the username
- for char in filter( lambda x: x not in string.ascii_lowercase + string.digits + '-', username ):
- username = username.replace( char, '-' )
- # Find a unique username - user can change it later
- if ( self.sa_session.query( self.app.model.User ).filter_by( username=username ).first() ):
- i = 1
- while ( self.sa_session.query( self.app.model.User ).filter_by( username=(username + '-' + str(i) ) ).first() ):
- i += 1
- username += '-' + str(i)
- user.username = username
- self.sa_session.add( user )
- self.sa_session.flush()
- self.app.security_agent.create_private_user_role( user )
- # We set default user permissions, before we log in and set the default history permissions
- if 'webapp' not in self.environ or self.environ['webapp'] != 'tool_shed':
- self.app.security_agent.user_set_default_permissions( user )
- # self.log_event( "Automatically created account '%s'", user.email )
- return user
-
- def __update_session_cookie( self, name='galaxysession' ):
- """
- Update the session cookie to match the current session.
- """
- self.set_cookie( self.security.encode_guid(self.galaxy_session.session_key ),
- name=name, path=self.app.config.cookie_path )
-
- def handle_user_login( self, user ):
- """
- Login a new user (possibly newly created)
-
- - create a new session
- - associate new session with user
- - if old session had a history and it was not associated with a user, associate it with the new session,
- otherwise associate the current session's history with the user
- - add the disk usage of the current session to the user's total disk usage
- """
- # Set the previous session
- prev_galaxy_session = self.galaxy_session
- prev_galaxy_session.is_valid = False
- # Define a new current_session
- self.galaxy_session = self.__create_new_session( prev_galaxy_session, user )
- if self.webapp.name == 'galaxy':
- cookie_name = 'galaxysession'
- # Associated the current user's last accessed history (if exists) with their new session
- history = None
- try:
- users_last_session = user.galaxy_sessions[0]
- last_accessed = True
- except:
- users_last_session = None
- last_accessed = False
- if (prev_galaxy_session.current_history and not
- prev_galaxy_session.current_history.deleted and
- prev_galaxy_session.current_history.datasets):
- if prev_galaxy_session.current_history.user is None or prev_galaxy_session.current_history.user == user:
- # If the previous galaxy session had a history, associate it with the new
- # session, but only if it didn't belong to a different user.
- history = prev_galaxy_session.current_history
- if prev_galaxy_session.user is None:
- # Increase the user's disk usage by the amount of the previous history's datasets if they didn't already own it.
- for hda in history.datasets:
- user.total_disk_usage += hda.quota_amount( user )
- elif self.galaxy_session.current_history:
- history = self.galaxy_session.current_history
- if (not history and users_last_session and
- users_last_session.current_history and not
- users_last_session.current_history.deleted):
- history = users_last_session.current_history
- elif not history:
- history = self.get_history( create=True )
- if history not in self.galaxy_session.histories:
- self.galaxy_session.add_history( history )
- if history.user is None:
- history.user = user
- self.galaxy_session.current_history = history
- if not last_accessed:
- # Only set default history permissions if current history is not from a previous session
- self.app.security_agent.history_set_default_permissions( history, dataset=True, bypass_manage_permission=True )
- self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session, history ) )
- else:
- cookie_name = 'galaxycommunitysession'
- self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session ) )
- self.sa_session.flush()
- # This method is not called from the Galaxy reports, so the cookie will always be galaxysession
- self.__update_session_cookie( name=cookie_name )
-
- def handle_user_logout( self, logout_all=False ):
- """
- Logout the current user:
- - invalidate the current session
- - create a new session with no user associated
- """
- prev_galaxy_session = self.galaxy_session
- prev_galaxy_session.is_valid = False
- self.galaxy_session = self.__create_new_session( prev_galaxy_session )
- self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session ) )
- galaxy_user_id = prev_galaxy_session.user_id
- if logout_all and galaxy_user_id is not None:
- for other_galaxy_session in self.sa_session.query( self.app.model.GalaxySession
- ).filter( and_( self.app.model.GalaxySession.table.c.user_id==galaxy_user_id, #noqa
- self.app.model.GalaxySession.table.c.is_valid==True, #noqa
- self.app.model.GalaxySession.table.c.id!=prev_galaxy_session.id ) ): #noqa
- other_galaxy_session.is_valid = False
- self.sa_session.add( other_galaxy_session )
- self.sa_session.flush()
- if self.webapp.name == 'galaxy':
- # This method is not called from the Galaxy reports, so the cookie will always be galaxysession
- self.__update_session_cookie( name='galaxysession' )
- elif self.webapp.name == 'tool_shed':
- self.__update_session_cookie( name='galaxycommunitysession' )
-
- def get_galaxy_session( self ):
- """
- Return the current galaxy session
- """
- return self.galaxy_session
-
- def get_history( self, create=False ):
- """
- Load the current history, creating a new one only if there is not
- current history and we're told to create.
- Transactions will not always have an active history (API requests), so
- None is a valid response.
- """
- history = None
- if self.galaxy_session:
- history = self.galaxy_session.current_history
- if not history and util.string_as_bool( create ):
- history = self.new_history()
- return history
-
- def set_history( self, history ):
- if history and not history.deleted:
- self.galaxy_session.current_history = history
- self.sa_session.add( self.galaxy_session )
- self.sa_session.flush()
-
- history = property( get_history, set_history )
-
- def get_or_create_default_history( self ):
- """
- Gets or creates a default history and associates it with the current
- session.
- """
-
- # There must be a user to fetch a default history.
- if not self.galaxy_session.user:
- return self.new_history()
-
- # Look for default history that (a) has default name + is not deleted and
- # (b) has no datasets. If suitable history found, use it; otherwise, create
- # new history.
- unnamed_histories = self.sa_session.query( self.app.model.History ).filter_by(
- user=self.galaxy_session.user,
- name=self.app.model.History.default_name,
- deleted=False )
- default_history = None
- for history in unnamed_histories:
- if len( history.datasets ) == 0:
- # Found suitable default history.
- default_history = history
- break
-
- # Set or create hsitory.
- if default_history:
- history = default_history
- self.set_history( history )
- else:
- history = self.new_history()
-
- return history
-
- def new_history( self, name=None ):
- """
- Create a new history and associate it with the current session and
- its associated user (if set).
- """
- # Create new history
- history = self.app.model.History()
- if name:
- history.name = name
- # Associate with session
- history.add_galaxy_session( self.galaxy_session )
- # Make it the session's current history
- self.galaxy_session.current_history = history
- # Associate with user
- if self.galaxy_session.user:
- history.user = self.galaxy_session.user
- # Track genome_build with history
- history.genome_build = self.app.genome_builds.default_value
- # Set the user's default history permissions
- self.app.security_agent.history_set_default_permissions( history )
- # Save
- self.sa_session.add_all( ( self.galaxy_session, history ) )
- self.sa_session.flush()
- return history
-
- @base.lazy_property
- def template_context( self ):
- return dict()
-
- def make_form_data( self, name, **kwargs ):
- rval = self.template_context[name] = FormData()
- rval.values.update( kwargs )
- return rval
-
- def set_message( self, message, type=None ):
- """
- Convenience method for setting the 'message' and 'message_type'
- element of the template context.
- """
- self.template_context['message'] = message
- if type:
- self.template_context['status'] = type
-
- def get_message( self ):
- """
- Convenience method for getting the 'message' element of the template
- context.
- """
- return self.template_context['message']
-
- def show_message( self, message, type='info', refresh_frames=[], cont=None, use_panels=False, active_view="" ):
- """
- Convenience method for displaying a simple page with a single message.
-
- `type`: one of "error", "warning", "info", or "done"; determines the
- type of dialog box and icon displayed with the message
-
- `refresh_frames`: names of frames in the interface that should be
- refreshed when the message is displayed
- """
- return self.fill_template( "message.mako", status=type, message=message, refresh_frames=refresh_frames, cont=cont, use_panels=use_panels, active_view=active_view )
-
- def show_error_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
- """
- Convenience method for displaying an error message. See `show_message`.
- """
- return self.show_message( message, 'error', refresh_frames, use_panels=use_panels, active_view=active_view )
-
- def show_ok_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
- """
- Convenience method for displaying an ok message. See `show_message`.
- """
- return self.show_message( message, 'done', refresh_frames, use_panels=use_panels, active_view=active_view )
-
- def show_warn_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
- """
- Convenience method for displaying an warn message. See `show_message`.
- """
- return self.show_message( message, 'warning', refresh_frames, use_panels=use_panels, active_view=active_view )
-
- def show_form( self, form, header=None, template="form.mako", use_panels=False, active_view="" ):
- """
- Convenience method for displaying a simple page with a single HTML
- form.
- """
- return self.fill_template( template, form=form, header=header,
- use_panels=( form.use_panels or use_panels ),
- active_view=active_view )
-
- def fill_template(self, filename, **kwargs):
- """
- Fill in a template, putting any keyword arguments on the context.
- """
- # call get_user so we can invalidate sessions from external users,
- # if external auth has been disabled.
- self.get_user()
- if filename.endswith( ".mako" ):
- return self.fill_template_mako( filename, **kwargs )
- else:
- template = Template( file=os.path.join(self.app.config.template_path, filename),
- searchList=[kwargs, self.template_context, dict(caller=self, t=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app)] )
- return str( template )
-
- def fill_template_mako( self, filename, template_lookup=None, **kwargs ):
- template_lookup = template_lookup or self.webapp.mako_template_lookup
- template = template_lookup.get_template( filename )
- template.output_encoding = 'utf-8'
-
- data = dict( caller=self, t=self, trans=self, h=helpers, util=util,
- request=self.request, response=self.response, app=self.app )
- data.update( self.template_context )
- data.update( kwargs )
- return template.render( **data )
-
- def stream_template_mako( self, filename, **kwargs ):
- template = self.webapp.mako_template_lookup.get_template( filename )
- template.output_encoding = 'utf-8'
- data = dict( caller=self, t=self, trans=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app )
- data.update( self.template_context )
- data.update( kwargs )
-
- def render( environ, start_response ):
- response_write = start_response( self.response.wsgi_status(), self.response.wsgi_headeritems() )
-
- class StreamBuffer( object ):
- def write( self, d ):
- response_write( d.encode( 'utf-8' ) )
- buffer = StreamBuffer()
- context = mako.runtime.Context( buffer, **data )
- template.render_context( context )
- return []
- return render
-
- def fill_template_string(self, template_string, context=None, **kwargs):
- """
- Fill in a template, putting any keyword arguments on the context.
- """
- template = Template( source=template_string,
- searchList=[context or kwargs, dict(caller=self)] )
- return str(template)
-
-
-class FormBuilder( object ):
- """
- Simple class describing an HTML form
- """
- def __init__( self, action="", title="", name="form", submit_text="submit", use_panels=False ):
- self.title = title
- self.name = name
- self.action = action
- self.submit_text = submit_text
- self.inputs = []
- self.use_panels = use_panels
-
- def add_input( self, type, name, label, value=None, error=None, help=None, use_label=True ):
- self.inputs.append( FormInput( type, label, name, value, error, help, use_label ) )
- return self
-
- def add_text( self, name, label, value=None, error=None, help=None ):
- return self.add_input( 'text', label, name, value, error, help )
-
- def add_password( self, name, label, value=None, error=None, help=None ):
- return self.add_input( 'password', label, name, value, error, help )
-
- def add_select( self, name, label, value=None, options=[], error=None, help=None, use_label=True ):
- self.inputs.append( SelectInput( name, label, value=value, options=options, error=error, help=help, use_label=use_label ) )
- return self
-
-
-class FormInput( object ):
- """
- Simple class describing a form input element
- """
- def __init__( self, type, name, label, value=None, error=None, help=None, use_label=True, extra_attributes={}, **kwargs ):
- self.type = type
- self.name = name
- self.label = label
- self.value = value
- self.error = error
- self.help = help
- self.use_label = use_label
- self.extra_attributes = extra_attributes
-
-
-class DatalistInput( FormInput ):
- """ Data list input """
-
- def __init__( self, name, *args, **kwargs ):
- if 'extra_attributes' not in kwargs:
- kwargs[ 'extra_attributes' ] = {}
- kwargs[ 'extra_attributes' ][ 'list' ] = name
- FormInput.__init__( self, None, name, *args, **kwargs )
- self.options = kwargs.get( 'options', {} )
-
- def body_html( self ):
- options = "".join( [ "<option value='%s'>%s</option>" % ( key, value ) for key, value in self.options.iteritems() ] )
- return """<datalist id="%s">%s</datalist>""" % ( self.name, options )
-
-
-class SelectInput( FormInput ):
- """ A select form input. """
- def __init__( self, name, label, value=None, options=[], error=None, help=None, use_label=True ):
- FormInput.__init__( self, "select", name, label, value=value, error=error, help=help, use_label=use_label )
- self.options = options
-
-
-class FormData( object ):
- """
- Class for passing data about a form to a template, very rudimentary, could
- be combined with the tool form handling to build something more general.
- """
- def __init__( self ):
- self.values = Bunch()
- self.errors = Bunch()
-
-
-class Bunch( dict ):
- """
- Bunch based on a dict
- """
- def __getattr__( self, key ):
- if key not in self:
- raise AttributeError(key)
- return self[key]
-
- def __setattr__( self, key, value ):
- self[key] = value
diff -r 012e6eb765684020eb597d8925e4ca5b58b9d904 -r d57655849ac41b2b1f90b73e2835d618df45e985 lib/galaxy/web/framework/decorators.py
--- /dev/null
+++ b/lib/galaxy/web/framework/decorators.py
@@ -0,0 +1,334 @@
+import inspect
+from traceback import format_exc
+from functools import wraps
+
+from galaxy import eggs
+eggs.require( "Paste" )
+import paste.httpexceptions
+
+from galaxy import util
+from galaxy.exceptions import error_codes
+from galaxy.exceptions import MessageException
+from galaxy.util.json import from_json_string, to_json_string
+
+import logging
+log = logging.getLogger( __name__ )
+
+
+JSON_CONTENT_TYPE = "application/json"
+
+
+def error( message ):
+ raise MessageException( message, type='error' )
+
+
+# ----------------------------------------------------------------------------- web controller decorators
+def _save_orig_fn( wrapped, orig ):
+ if not hasattr( orig, '_orig' ):
+ wrapped._orig = orig
+ return wrapped
+
+def expose( func ):
+ """
+ Decorator: mark a function as 'exposed' and thus web accessible
+ """
+ func.exposed = True
+ return func
+
+def json( func, **json_kwargs ):
+ """
+ Format the response as JSON and set the response content type to
+ JSON_CONTENT_TYPE.
+ """
+ @wraps(func)
+ def call_and_format( self, trans, *args, **kwargs ):
+ trans.response.set_content_type( JSON_CONTENT_TYPE )
+ return to_json_string( func( self, trans, *args, **kwargs ), **json_kwargs )
+ if not hasattr( func, '_orig' ):
+ call_and_format._orig = func
+ return expose( _save_orig_fn( call_and_format, func ) )
+
+def json_pretty( func ):
+ """
+ Indent and sort returned JSON.
+ """
+ return json( func, indent=4, sort_keys=True )
+
+def require_login( verb="perform this action", use_panels=False, webapp='galaxy' ):
+ def argcatcher( func ):
+ @wraps(func)
+ def decorator( self, trans, *args, **kwargs ):
+ if trans.get_user():
+ return func( self, trans, *args, **kwargs )
+ else:
+ return trans.show_error_message(
+ 'You must be <a target="galaxy_main" href="%s">logged in</a> to %s.'
+ % ( url_for( controller='user', action='login', webapp=webapp ), verb ), use_panels=use_panels )
+ return decorator
+ return argcatcher
+
+def require_admin( func ):
+ @wraps(func)
+ def decorator( self, trans, *args, **kwargs ):
+ if not trans.user_is_admin():
+ msg = "You must be an administrator to access this feature."
+ user = trans.get_user()
+ if not trans.app.config.admin_users_list:
+ msg = "You must be logged in as an administrator to access this feature, but no administrators are set in the Galaxy configuration."
+ elif not user:
+ msg = "You must be logged in as an administrator to access this feature."
+ trans.response.status = 403
+ if trans.response.get_content_type() == 'application/json':
+ return msg
+ else:
+ return trans.show_error_message( msg )
+ return func( self, trans, *args, **kwargs )
+ return decorator
+
+
+# ----------------------------------------------------------------------------- (original) api decorators
+def expose_api( func, to_json=True, user_required=True ):
+ """
+ Expose this function via the API.
+ """
+ @wraps(func)
+ def decorator( self, trans, *args, **kwargs ):
+ def error( environ, start_response ):
+ start_response( error_status, [('Content-type', 'text/plain')] )
+ return error_message
+ error_status = '403 Forbidden'
+ if trans.error_message:
+ return trans.error_message
+ if user_required and trans.anonymous:
+ error_message = "API Authentication Required for this request"
+ return error
+ if trans.request.body:
+ try:
+ kwargs['payload'] = __extract_payload_from_request(trans, func, kwargs)
+ except ValueError:
+ error_status = '400 Bad Request'
+ error_message = 'Your request did not appear to be valid JSON, please consult the API documentation'
+ return error
+ trans.response.set_content_type( "application/json" )
+ # send 'do not cache' headers to handle IE's caching of ajax get responses
+ trans.response.headers[ 'Cache-Control' ] = "max-age=0,no-cache,no-store"
+ # Perform api_run_as processing, possibly changing identity
+ if 'payload' in kwargs and 'run_as' in kwargs['payload']:
+ if not trans.user_can_do_run_as():
+ error_message = 'User does not have permissions to run jobs as another user'
+ return error
+ try:
+ decoded_user_id = trans.security.decode_id( kwargs['payload']['run_as'] )
+ except TypeError:
+ trans.response.status = 400
+ return "Malformed user id ( %s ) specified, unable to decode." % str( kwargs['payload']['run_as'] )
+ try:
+ user = trans.sa_session.query( trans.app.model.User ).get( decoded_user_id )
+ trans.api_inherit_admin = trans.user_is_admin()
+ trans.set_user(user)
+ except:
+ trans.response.status = 400
+ return "That user does not exist."
+ try:
+ rval = func( self, trans, *args, **kwargs)
+ if to_json and trans.debug:
+ rval = to_json_string( rval, indent=4, sort_keys=True )
+ elif to_json:
+ rval = to_json_string( rval )
+ return rval
+ except paste.httpexceptions.HTTPException:
+ raise # handled
+ except:
+ log.exception( 'Uncaught exception in exposed API method:' )
+ raise paste.httpexceptions.HTTPServerError()
+ return expose( _save_orig_fn( decorator, func ) )
+
+def __extract_payload_from_request(trans, func, kwargs):
+ content_type = trans.request.headers['content-type']
+ if content_type.startswith('application/x-www-form-urlencoded') or content_type.startswith('multipart/form-data'):
+ # If the content type is a standard type such as multipart/form-data, the wsgi framework parses the request body
+ # and loads all field values into kwargs. However, kwargs also contains formal method parameters etc. which
+ # are not a part of the request body. This is a problem because it's not possible to differentiate between values
+ # which are a part of the request body, and therefore should be a part of the payload, and values which should not be
+ # in the payload. Therefore, the decorated method's formal arguments are discovered through reflection and removed from
+ # the payload dictionary. This helps to prevent duplicate argument conflicts in downstream methods.
+ payload = kwargs.copy()
+ named_args, _, _, _ = inspect.getargspec(func)
+ for arg in named_args:
+ payload.pop(arg, None)
+ for k, v in payload.iteritems():
+ if isinstance(v, (str, unicode)):
+ try:
+ payload[k] = from_json_string(v)
+ except:
+ # may not actually be json, just continue
+ pass
+ payload = util.recursively_stringify_dictionary_keys( payload )
+ else:
+ # Assume application/json content type and parse request body manually, since wsgi won't do it. However, the order of this check
+ # should ideally be in reverse, with the if clause being a check for application/json and the else clause assuming a standard encoding
+ # such as multipart/form-data. Leaving it as is for backward compatibility, just in case.
+ payload = util.recursively_stringify_dictionary_keys( from_json_string( trans.request.body ) )
+ return payload
+
+def expose_api_raw( func ):
+ """
+ Expose this function via the API but don't dump the results
+ to JSON.
+ """
+ return expose_api( func, to_json=False )
+
+def expose_api_raw_anonymous( func ):
+ """
+ Expose this function via the API but don't dump the results
+ to JSON.
+ """
+ return expose_api( func, to_json=False, user_required=False )
+
+def expose_api_anonymous( func, to_json=True ):
+ """
+ Expose this function via the API but don't require a set user.
+ """
+ return expose_api( func, to_json=to_json, user_required=False )
+
+
+# ----------------------------------------------------------------------------- (new) api decorators
+# TODO: rename as expose_api and make default.
+def _future_expose_api( func, to_json=True, user_required=True ):
+ """
+ Expose this function via the API.
+ """
+ @wraps(func)
+ def decorator( self, trans, *args, **kwargs ):
+ if trans.error_message:
+ # TODO: Document this branch, when can this happen,
+ # I don't understand it.
+ return __api_error_response( trans, err_msg=trans.error_message )
+ if user_required and trans.anonymous:
+ error_code = error_codes.USER_NO_API_KEY
+ # Use error codes default error message.
+ err_msg = "API authentication required for this request"
+ return __api_error_response( trans, err_code=error_code, err_msg=err_msg, status_code=403 )
+ if trans.request.body:
+ try:
+ kwargs['payload'] = __extract_payload_from_request(trans, func, kwargs)
+ except ValueError:
+ error_code = error_codes.USER_INVALID_JSON
+ return __api_error_response( trans, status_code=400, err_code=error_code )
+
+ trans.response.set_content_type( JSON_CONTENT_TYPE )
+ # send 'do not cache' headers to handle IE's caching of ajax get responses
+ trans.response.headers[ 'Cache-Control' ] = "max-age=0,no-cache,no-store"
+ # TODO: Refactor next block out into a helper procedure.
+ # Perform api_run_as processing, possibly changing identity
+ if 'payload' in kwargs and 'run_as' in kwargs['payload']:
+ if not trans.user_can_do_run_as():
+ error_code = error_codes.USER_CANNOT_RUN_AS
+ return __api_error_response( trans, err_code=error_code, status_code=403 )
+ try:
+ decoded_user_id = trans.security.decode_id( kwargs['payload']['run_as'] )
+ except TypeError:
+ error_message = "Malformed user id ( %s ) specified, unable to decode." % str( kwargs['payload']['run_as'] )
+ error_code = error_codes.USER_INVALID_RUN_AS
+ return __api_error_response( trans, err_code=error_code, err_msg=error_message, status_code=400)
+ try:
+ user = trans.sa_session.query( trans.app.model.User ).get( decoded_user_id )
+ trans.api_inherit_admin = trans.user_is_admin()
+ trans.set_user(user)
+ except:
+ error_code = error_codes.USER_INVALID_RUN_AS
+ return __api_error_response( trans, err_code=error_code, status_code=400 )
+ try:
+ rval = func( self, trans, *args, **kwargs)
+ if to_json and trans.debug:
+ rval = to_json_string( rval, indent=4, sort_keys=True )
+ elif to_json:
+ rval = to_json_string( rval )
+ return rval
+ except MessageException as e:
+ traceback_string = format_exc()
+ return __api_error_response( trans, exception=e, traceback=traceback_string )
+ except paste.httpexceptions.HTTPException:
+ # TODO: Allow to pass or format for the API???
+ raise # handled
+ except Exception as e:
+ traceback_string = format_exc()
+ error_message = 'Uncaught exception in exposed API method:'
+ log.exception( error_message )
+ return __api_error_response(
+ trans,
+ status_code=500,
+ exception=e,
+ traceback=traceback_string,
+ err_msg=error_message,
+ err_code=error_codes.UNKNOWN
+ )
+ if not hasattr(func, '_orig'):
+ decorator._orig = func
+ decorator.exposed = True
+ return decorator
+
+def __api_error_message( trans, **kwds ):
+ exception = kwds.get( "exception", None )
+ if exception:
+ # If we are passed a MessageException use err_msg.
+ default_error_code = getattr( exception, "err_code", error_codes.UNKNOWN )
+ default_error_message = getattr( exception, "err_msg", default_error_code.default_error_message )
+ extra_error_info = getattr( exception, 'extra_error_info', {} )
+ if not isinstance( extra_error_info, dict ):
+ extra_error_info = {}
+ else:
+ default_error_message = "Error processing API request."
+ default_error_code = error_codes.UNKNOWN
+ extra_error_info = {}
+ traceback_string = kwds.get( "traceback", "No traceback available." )
+ err_msg = kwds.get( "err_msg", default_error_message )
+ error_code_object = kwds.get( "err_code", default_error_code )
+ try:
+ error_code = error_code_object.code
+ except AttributeError:
+ # Some sort of bad error code sent in, logic failure on part of
+ # Galaxy developer.
+ error_code = error_codes.UNKNOWN.code
+ # Would prefer the terminology of error_code and error_message, but
+ # err_msg used a good number of places already. Might as well not change
+ # it?
+ error_response = dict( err_msg=err_msg, err_code=error_code, **extra_error_info )
+ if trans.debug: # TODO: Should admins get to see traceback as well?
+ error_response[ "traceback" ] = traceback_string
+ return error_response
+
+def __api_error_response( trans, **kwds ):
+ error_dict = __api_error_message( trans, **kwds )
+ exception = kwds.get( "exception", None )
+ # If we are given an status code directly - use it - otherwise check
+ # the exception for a status_code attribute.
+ if "status_code" in kwds:
+ status_code = int( kwds.get( "status_code" ) )
+ elif hasattr( exception, "status_code" ):
+ status_code = int( exception.status_code )
+ else:
+ status_code = 500
+ response = trans.response
+ if not response.status or str(response.status).startswith("20"):
+ # Unset status code appears to be string '200 OK', if anything
+ # non-success (i.e. not 200 or 201) has been set, do not override
+ # underlying controller.
+ response.status = status_code
+ return to_json_string( error_dict )
+
+
+# TODO: rename as expose_api and make default.
+def _future_expose_api_anonymous( func, to_json=True ):
+ """
+ Expose this function via the API but don't require a set user.
+ """
+ return _future_expose_api( func, to_json=to_json, user_required=False )
+
+
+def _future_expose_api_raw( func ):
+ return _future_expose_api( func, to_json=False, user_required=True )
+
+
+def _future_expose_api_raw_anonymous( func ):
+ return _future_expose_api( func, to_json=False, user_required=False )
diff -r 012e6eb765684020eb597d8925e4ca5b58b9d904 -r d57655849ac41b2b1f90b73e2835d618df45e985 lib/galaxy/web/framework/formbuilder.py
--- /dev/null
+++ b/lib/galaxy/web/framework/formbuilder.py
@@ -0,0 +1,93 @@
+from galaxy.util import bunch
+
+import logging
+log = logging.getLogger( __name__ )
+
+#class Bunch( dict ):
+# """
+# Bunch based on a dict
+# """
+# def __getattr__( self, key ):
+# if key not in self:
+# raise AttributeError(key)
+# return self[key]
+#
+# def __setattr__( self, key, value ):
+# self[key] = value
+
+def form( *args, **kwargs ):
+ return FormBuilder( *args, **kwargs )
+
+class FormBuilder( object ):
+ """
+ Simple class describing an HTML form
+ """
+ def __init__( self, action="", title="", name="form", submit_text="submit", use_panels=False ):
+ self.title = title
+ self.name = name
+ self.action = action
+ self.submit_text = submit_text
+ self.inputs = []
+ self.use_panels = use_panels
+
+ def add_input( self, type, name, label, value=None, error=None, help=None, use_label=True ):
+ self.inputs.append( FormInput( type, label, name, value, error, help, use_label ) )
+ return self
+
+ def add_text( self, name, label, value=None, error=None, help=None ):
+ return self.add_input( 'text', label, name, value, error, help )
+
+ def add_password( self, name, label, value=None, error=None, help=None ):
+ return self.add_input( 'password', label, name, value, error, help )
+
+ def add_select( self, name, label, value=None, options=[], error=None, help=None, use_label=True ):
+ self.inputs.append( SelectInput( name, label, value=value, options=options, error=error, help=help, use_label=use_label ) )
+ return self
+
+
+class FormInput( object ):
+ """
+ Simple class describing a form input element
+ """
+ def __init__( self, type, name, label, value=None, error=None, help=None, use_label=True, extra_attributes={}, **kwargs ):
+ self.type = type
+ self.name = name
+ self.label = label
+ self.value = value
+ self.error = error
+ self.help = help
+ self.use_label = use_label
+ self.extra_attributes = extra_attributes
+
+
+class DatalistInput( FormInput ):
+ """ Data list input """
+
+ def __init__( self, name, *args, **kwargs ):
+ if 'extra_attributes' not in kwargs:
+ kwargs[ 'extra_attributes' ] = {}
+ kwargs[ 'extra_attributes' ][ 'list' ] = name
+ FormInput.__init__( self, None, name, *args, **kwargs )
+ self.options = kwargs.get( 'options', {} )
+
+ def body_html( self ):
+ options = "".join( [ "<option value='%s'>%s</option>" % ( key, value ) for key, value in self.options.iteritems() ] )
+ return """<datalist id="%s">%s</datalist>""" % ( self.name, options )
+
+
+class SelectInput( FormInput ):
+ """ A select form input. """
+ def __init__( self, name, label, value=None, options=[], error=None, help=None, use_label=True ):
+ FormInput.__init__( self, "select", name, label, value=value, error=error, help=help, use_label=use_label )
+ self.options = options
+
+
+class FormData( object ):
+ """
+ Class for passing data about a form to a template, very rudimentary, could
+ be combined with the tool form handling to build something more general.
+ """
+ def __init__( self ):
+#TODO: galaxy's two Bunchs are defined differently. Is this right?
+ self.values = bunch.Bunch()
+ self.errors = bunch.Bunch()
diff -r 012e6eb765684020eb597d8925e4ca5b58b9d904 -r d57655849ac41b2b1f90b73e2835d618df45e985 lib/galaxy/web/framework/helpers/grids.py
--- a/lib/galaxy/web/framework/helpers/grids.py
+++ b/lib/galaxy/web/framework/helpers/grids.py
@@ -5,7 +5,8 @@
from galaxy.util import sanitize_text
from galaxy.util.json import from_json_string, to_json_string
from galaxy.util.odict import odict
-from galaxy.web.framework import error, url_for
+from galaxy.web.framework import decorators
+from galaxy.web.framework import url_for
from galaxy.web.framework.helpers import iff
from sqlalchemy.sql.expression import and_, func, or_
@@ -317,7 +318,7 @@
try:
id = map( int, id )
except:
- error( "Invalid id" )
+ decorators.error( "Invalid id" )
return id
# ---- Override these ----------------------------------------------------
def handle_operation( self, trans, operation, ids, **kwargs ):
This diff is so big that we needed to truncate the remainder.
https://bitbucket.org/galaxy/galaxy-central/commits/360d9d7839d7/
Changeset: 360d9d7839d7
User: carlfeberhard
Date: 2014-09-03 15:59:59
Summary: merge central
Affected #: 3 files
diff -r d57655849ac41b2b1f90b73e2835d618df45e985 -r 360d9d7839d74f01a2da5b35fb1be10464956112 lib/galaxy/app.py
--- a/lib/galaxy/app.py
+++ b/lib/galaxy/app.py
@@ -96,11 +96,9 @@
# Load history import/export tools.
load_history_imp_exp_tools( self.toolbox )
# visualizations registry: associates resources with visualizations, controls how to render
- self.visualizations_registry = None
- if self.config.visualization_plugins_directory:
- self.visualizations_registry = VisualizationsRegistry( self,
- directories_setting=self.config.visualization_plugins_directory,
- template_cache_dir=self.config.template_cache )
+ self.visualizations_registry = VisualizationsRegistry( self,
+ directories_setting=self.config.visualization_plugins_directory,
+ template_cache_dir=self.config.template_cache )
# Load security policy.
self.security_agent = self.model.security_agent
self.host_security_agent = galaxy.security.HostAgent( model=self.security_agent.model, permitted_actions=self.security_agent.permitted_actions )
diff -r d57655849ac41b2b1f90b73e2835d618df45e985 -r 360d9d7839d74f01a2da5b35fb1be10464956112 lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -376,8 +376,9 @@
self.fluent_log = string_as_bool( kwargs.get( 'fluent_log', False ) )
self.fluent_host = kwargs.get( 'fluent_host', 'localhost' )
self.fluent_port = int( kwargs.get( 'fluent_port', 24224 ) )
- # visualization plugin framework
- self.visualization_plugins_directory = kwargs.get( 'visualization_plugins_directory', None )
+ # directory where the visualization/registry searches for plugins
+ self.visualization_plugins_directory = kwargs.get(
+ 'visualization_plugins_directory', 'config/plugins/visualizations' )
# Default chunk size for chunkable datatypes -- 64k
self.display_chunk_size = int( kwargs.get( 'display_chunk_size', 65536) )
diff -r d57655849ac41b2b1f90b73e2835d618df45e985 -r 360d9d7839d74f01a2da5b35fb1be10464956112 universe_wsgi.ini.sample
--- a/universe_wsgi.ini.sample
+++ b/universe_wsgi.ini.sample
@@ -210,8 +210,8 @@
# Visualizations config directory: where to look for individual visualization plugins.
# The path is relative to the Galaxy root dir. To use an absolute path begin the path
-# with '/'.
-visualization_plugins_directory = config/plugins/visualizations
+# with '/'. Defaults to "config/plugins/visualizations".
+#visualization_plugins_directory = config/plugins/visualizations
# Each job is given a unique empty directory as its current working directory.
# This option defines in what parent directory those directories will be
https://bitbucket.org/galaxy/galaxy-central/commits/a0c16c8930b7/
Changeset: a0c16c8930b7
User: carlfeberhard
Date: 2014-09-03 17:22:15
Summary: Combine transaction into webapp, move trans mixins to managers/context.py
Affected #: 3 files
diff -r 360d9d7839d74f01a2da5b35fb1be10464956112 -r a0c16c8930b76cfe98574b2e8b3311f7b2504361 lib/galaxy/web/base/pluginframework.py
--- a/lib/galaxy/web/base/pluginframework.py
+++ b/lib/galaxy/web/base/pluginframework.py
@@ -579,7 +579,7 @@
"""
Pass control over to trans and render ``template_filename``.
- :type trans: ``galaxy.web.framework.transaction.GalaxyWebTransaction``
+ :type trans: ``galaxy.web.framework.webapp.GalaxyWebTransaction``
:param trans: transaction doing the rendering
:type plugin: ``util.bunch.Bunch``
:param plugin: the plugin containing the template to render
diff -r 360d9d7839d74f01a2da5b35fb1be10464956112 -r a0c16c8930b76cfe98574b2e8b3311f7b2504361 lib/galaxy/web/framework/transaction.py
--- a/lib/galaxy/web/framework/transaction.py
+++ /dev/null
@@ -1,868 +0,0 @@
-import hashlib
-import os
-import random
-import socket
-import string
-import time
-from Cookie import CookieError
-
-from galaxy import eggs
-eggs.require( "Cheetah" )
-from Cheetah.Template import Template
-eggs.require( "Mako" )
-import mako.runtime
-eggs.require( "pytz" ) # Used by Babel.
-eggs.require( "Babel" )
-from babel.support import Translations
-from babel import Locale
-eggs.require( "SQLAlchemy >= 0.4" )
-from sqlalchemy import and_
-from sqlalchemy.orm.exc import NoResultFound
-
-from galaxy import util
-from galaxy.util import asbool
-from galaxy.util import safe_str_cmp
-from galaxy.util.json import to_json_string
-from galaxy.web.framework import base
-from galaxy.web.framework import helpers
-
-import logging
-log = logging.getLogger( __name__ )
-
-
-UCSC_SERVERS = (
- 'hgw1.cse.ucsc.edu',
- 'hgw2.cse.ucsc.edu',
- 'hgw3.cse.ucsc.edu',
- 'hgw4.cse.ucsc.edu',
- 'hgw5.cse.ucsc.edu',
- 'hgw6.cse.ucsc.edu',
- 'hgw7.cse.ucsc.edu',
- 'hgw8.cse.ucsc.edu',
-)
-
-
-class ProvidesAppContext( object ):
- """ For transaction-like objects to provide Galaxy convience layer for
- database and event handling.
-
- Mixed in class must provide `app` property.
- """
-
- def log_action( self, user=None, action=None, context=None, params=None):
- """
- Application-level logging of user actions.
- """
- if self.app.config.log_actions:
- action = self.app.model.UserAction(action=action, context=context, params=unicode( to_json_string( params ) ) )
- try:
- if user:
- action.user = user
- else:
- action.user = self.user
- except:
- action.user = None
- try:
- action.session_id = self.galaxy_session.id
- except:
- action.session_id = None
- self.sa_session.add( action )
- self.sa_session.flush()
-
- def log_event( self, message, tool_id=None, **kwargs ):
- """
- Application level logging. Still needs fleshing out (log levels and such)
- Logging events is a config setting - if False, do not log.
- """
- if self.app.config.log_events:
- event = self.app.model.Event()
- event.tool_id = tool_id
- try:
- event.message = message % kwargs
- except:
- event.message = message
- try:
- event.history = self.get_history()
- except:
- event.history = None
- try:
- event.history_id = self.history.id
- except:
- event.history_id = None
- try:
- event.user = self.user
- except:
- event.user = None
- try:
- event.session_id = self.galaxy_session.id
- except:
- event.session_id = None
- self.sa_session.add( event )
- self.sa_session.flush()
-
- @property
- def sa_session( self ):
- """
- Returns a SQLAlchemy session -- currently just gets the current
- session from the threadlocal session context, but this is provided
- to allow migration toward a more SQLAlchemy 0.4 style of use.
- """
- return self.app.model.context.current
-
- def expunge_all( self ):
- app = self.app
- context = app.model.context
- context.expunge_all()
- # This is a bit hacky, should refctor this. Maybe refactor to app -> expunge_all()
- if hasattr(app, 'install_model'):
- install_model = app.install_model
- if install_model != app.model:
- install_model.context.expunge_all()
-
- def get_toolbox(self):
- """Returns the application toolbox"""
- return self.app.toolbox
-
- @property
- def model( self ):
- return self.app.model
-
- @property
- def install_model( self ):
- return self.app.install_model
-
- def request_types(self):
- if self.sa_session.query( self.app.model.RequestType ).filter_by( deleted=False ).count() > 0:
- return True
- return False
-
-
-class ProvidesUserContext( object ):
- """ For transaction-like objects to provide Galaxy convience layer for
- reasoning about users.
-
- Mixed in class must provide `user`, `api_inherit_admin`, and `app`
- properties.
- """
-
- @property
- def anonymous( self ):
- return self.user is None and not self.api_inherit_admin
-
- def get_current_user_roles( self ):
- user = self.user
- if user:
- roles = user.all_roles()
- else:
- roles = []
- return roles
-
- def user_is_admin( self ):
- if self.api_inherit_admin:
- return True
- return self.user and self.user.email in self.app.config.admin_users_list
-
- def user_can_do_run_as( self ):
- run_as_users = [ user for user in self.app.config.get( "api_allow_run_as", "" ).split( "," ) if user ]
- if not run_as_users:
- return False
- user_in_run_as_users = self.user and self.user.email in run_as_users
- # Can do if explicitly in list or master_api_key supplied.
- can_do_run_as = user_in_run_as_users or self.api_inherit_admin
- return can_do_run_as
-
- @property
- def user_ftp_dir( self ):
- identifier = self.app.config.ftp_upload_dir_identifier
- return os.path.join( self.app.config.ftp_upload_dir, getattr(self.user, identifier) )
-
-
-class ProvidesHistoryContext( object ):
- """ For transaction-like objects to provide Galaxy convience layer for
- reasoning about histories.
-
- Mixed in class must provide `user`, `history`, and `app`
- properties.
- """
-
- def db_dataset_for( self, dbkey ):
- """
- Returns the db_file dataset associated/needed by `dataset`, or `None`.
- """
- # If no history, return None.
- if self.history is None:
- return None
-#TODO: when does this happen is it Bunch or util.bunch.Bunch
- if isinstance(self.history, util.bunch.Bunch):
- # The API presents a Bunch for a history. Until the API is
- # more fully featured for handling this, also return None.
- return None
- datasets = self.sa_session.query( self.app.model.HistoryDatasetAssociation ) \
- .filter_by( deleted=False, history_id=self.history.id, extension="len" )
- for ds in datasets:
- if dbkey == ds.dbkey:
- return ds
- return None
-
- @property
- def db_builds( self ):
- """
- Returns the builds defined by galaxy and the builds defined by
- the user (chromInfo in history).
- """
- # FIXME: This method should be removed
- return self.app.genome_builds.get_genome_build_names( trans=self )
-
-
-class GalaxyWebTransaction( base.DefaultWebTransaction, ProvidesAppContext, ProvidesUserContext, ProvidesHistoryContext ):
- """
- Encapsulates web transaction specific state for the Galaxy application
- (specifically the user's "cookie" session and history)
- """
-
- def __init__( self, environ, app, webapp, session_cookie=None):
- self.app = app
- self.webapp = webapp
- self.security = webapp.security
- base.DefaultWebTransaction.__init__( self, environ )
- self.setup_i18n()
- self.expunge_all()
- self.debug = asbool( self.app.config.get( 'debug', False ) )
- # Flag indicating whether we are in workflow building mode (means
- # that the current history should not be used for parameter values
- # and such).
- self.workflow_building_mode = False
- # Flag indicating whether this is an API call and the API key user is an administrator
- self.api_inherit_admin = False
- self.__user = None
- self.galaxy_session = None
- self.error_message = None
-
- if self.environ.get('is_api_request', False):
- # With API requests, if there's a key, use it and associate the
- # user with the transaction.
- # If not, check for an active session but do not create one.
- # If an error message is set here, it's sent back using
- # trans.show_error in the response -- in expose_api.
- self.error_message = self._authenticate_api( session_cookie )
- elif self.app.name == "reports":
- self.galaxy_session = None
- else:
- # This is a web request, get or create session.
- self._ensure_valid_session( session_cookie )
- if self.galaxy_session:
- # When we've authenticated by session, we have to check the
- # following.
- # Prevent deleted users from accessing Galaxy
- if self.app.config.use_remote_user and self.galaxy_session.user.deleted:
- self.response.send_redirect( url_for( '/static/user_disabled.html' ) )
- if self.app.config.require_login:
- self._ensure_logged_in_user( environ, session_cookie )
-
- def setup_i18n( self ):
- locales = []
- if 'HTTP_ACCEPT_LANGUAGE' in self.environ:
- # locales looks something like: ['en', 'en-us;q=0.7', 'ja;q=0.3']
- client_locales = self.environ['HTTP_ACCEPT_LANGUAGE'].split( ',' )
- for locale in client_locales:
- try:
- locales.append( Locale.parse( locale.split( ';' )[0].strip(), sep='-' ).language )
- except Exception, e:
- log.debug( "Error parsing locale '%s'. %s: %s", locale, type( e ), e )
- if not locales:
- # Default to English
- locales = 'en'
- t = Translations.load( dirname='locale', locales=locales, domain='ginga' )
- self.template_context.update( dict( _=t.ugettext, n_=t.ugettext, N_=t.ungettext ) )
-
- def get_user( self ):
- """Return the current user if logged in or None."""
- if self.galaxy_session:
- return self.galaxy_session.user
- else:
- return self.__user
-
- def set_user( self, user ):
- """Set the current user."""
- if self.galaxy_session:
- self.galaxy_session.user = user
- self.sa_session.add( self.galaxy_session )
- self.sa_session.flush()
- self.__user = user
-
- user = property( get_user, set_user )
-
- def get_cookie( self, name='galaxysession' ):
- """Convenience method for getting a session cookie"""
- try:
- # If we've changed the cookie during the request return the new value
- if name in self.response.cookies:
- return self.response.cookies[name].value
- else:
- return self.request.cookies[name].value
- except:
- return None
-
- def set_cookie( self, value, name='galaxysession', path='/', age=90, version='1' ):
- """Convenience method for setting a session cookie"""
- # The galaxysession cookie value must be a high entropy 128 bit random number encrypted
- # using a server secret key. Any other value is invalid and could pose security issues.
- self.response.cookies[name] = value
- self.response.cookies[name]['path'] = path
- self.response.cookies[name]['max-age'] = 3600 * 24 * age # 90 days
- tstamp = time.localtime( time.time() + 3600 * 24 * age )
- self.response.cookies[name]['expires'] = time.strftime( '%a, %d-%b-%Y %H:%M:%S GMT', tstamp )
- self.response.cookies[name]['version'] = version
- try:
- self.response.cookies[name]['httponly'] = True
- except CookieError, e:
- log.warning( "Error setting httponly attribute in cookie '%s': %s" % ( name, e ) )
-
- def _authenticate_api( self, session_cookie ):
- """
- Authenticate for the API via key or session (if available).
- """
- api_key = self.request.params.get('key', None)
- secure_id = self.get_cookie( name=session_cookie )
- api_key_supplied = self.environ.get('is_api_request', False) and api_key
- if api_key_supplied and self._check_master_api_key( api_key ):
- self.api_inherit_admin = True
- log.info( "Session authenticated using Galaxy master api key" )
- self.user = None
- self.galaxy_session = None
- elif api_key_supplied:
- # Sessionless API transaction, we just need to associate a user.
- try:
- provided_key = self.sa_session.query( self.app.model.APIKeys ).filter( self.app.model.APIKeys.table.c.key == api_key ).one()
- except NoResultFound:
- return 'Provided API key is not valid.'
- if provided_key.user.deleted:
- return 'User account is deactivated, please contact an administrator.'
- newest_key = provided_key.user.api_keys[0]
- if newest_key.key != provided_key.key:
- return 'Provided API key has expired.'
- self.set_user( provided_key.user )
- elif secure_id:
- # API authentication via active session
- # Associate user using existing session
- self._ensure_valid_session( session_cookie )
- else:
- # Anonymous API interaction -- anything but @expose_api_anonymous will fail past here.
- self.user = None
- self.galaxy_session = None
-
- def _check_master_api_key( self, api_key ):
- master_api_key = getattr( self.app.config, 'master_api_key', None )
- if not master_api_key:
- return False
- # Hash keys to make them the same size, so we can do safe comparison.
- master_hash = hashlib.sha256( master_api_key ).hexdigest()
- provided_hash = hashlib.sha256( api_key ).hexdigest()
- return safe_str_cmp( master_hash, provided_hash )
-
- def _ensure_valid_session( self, session_cookie, create=True):
- """
- Ensure that a valid Galaxy session exists and is available as
- trans.session (part of initialization)
-
- Support for universe_session and universe_user cookies has been
- removed as of 31 Oct 2008.
- """
- # Try to load an existing session
- secure_id = self.get_cookie( name=session_cookie )
- galaxy_session = None
- prev_galaxy_session = None
- user_for_new_session = None
- invalidate_existing_session = False
- # Track whether the session has changed so we can avoid calling flush
- # in the most common case (session exists and is valid).
- galaxy_session_requires_flush = False
- if secure_id:
- # Decode the cookie value to get the session_key
- session_key = self.security.decode_guid( secure_id )
- try:
- # Make sure we have a valid UTF-8 string
- session_key = session_key.encode( 'utf8' )
- except UnicodeDecodeError:
- # We'll end up creating a new galaxy_session
- session_key = None
- if session_key:
- # Retrieve the galaxy_session id via the unique session_key
- galaxy_session = self.sa_session.query( self.app.model.GalaxySession ) \
- .filter( and_( self.app.model.GalaxySession.table.c.session_key==session_key, #noqa
- self.app.model.GalaxySession.table.c.is_valid==True ) ).first() #noqa
- # If remote user is in use it can invalidate the session and in some
- # cases won't have a cookie set above, so we need to to check some
- # things now.
- if self.app.config.use_remote_user:
- # If this is an api request, and they've passed a key, we let this go.
- assert self.app.config.remote_user_header in self.environ, \
- "use_remote_user is set but %s header was not provided" % self.app.config.remote_user_header
- remote_user_email = self.environ[ self.app.config.remote_user_header ]
- if getattr( self.app.config, "normalize_remote_user_email", False ):
- remote_user_email = remote_user_email.lower()
- if galaxy_session:
- # An existing session, make sure correct association exists
- if galaxy_session.user is None:
- # No user, associate
- galaxy_session.user = self.get_or_create_remote_user( remote_user_email )
- galaxy_session_requires_flush = True
- elif ((galaxy_session.user.email != remote_user_email) and
- ((not self.app.config.allow_user_impersonation) or
- (remote_user_email not in self.app.config.admin_users_list))):
- # Session exists but is not associated with the correct
- # remote user, and the currently set remote_user is not a
- # potentially impersonating admin.
- invalidate_existing_session = True
- user_for_new_session = self.get_or_create_remote_user( remote_user_email )
- log.warning( "User logged in as '%s' externally, but has a cookie as '%s' invalidating session",
- remote_user_email, galaxy_session.user.email )
- else:
- # No session exists, get/create user for new session
- user_for_new_session = self.get_or_create_remote_user( remote_user_email )
- else:
- if galaxy_session is not None and galaxy_session.user and galaxy_session.user.external:
- # Remote user support is not enabled, but there is an existing
- # session with an external user, invalidate
- invalidate_existing_session = True
- log.warning( "User '%s' is an external user with an existing session, invalidating session since external auth is disabled",
- galaxy_session.user.email )
- elif galaxy_session is not None and galaxy_session.user is not None and galaxy_session.user.deleted:
- invalidate_existing_session = True
- log.warning( "User '%s' is marked deleted, invalidating session" % galaxy_session.user.email )
- # Do we need to invalidate the session for some reason?
- if invalidate_existing_session:
- prev_galaxy_session = galaxy_session
- prev_galaxy_session.is_valid = False
- galaxy_session = None
- # No relevant cookies, or couldn't find, or invalid, so create a new session
- if galaxy_session is None:
- galaxy_session = self.__create_new_session( prev_galaxy_session, user_for_new_session )
- galaxy_session_requires_flush = True
- self.galaxy_session = galaxy_session
- self.__update_session_cookie( name=session_cookie )
- else:
- self.galaxy_session = galaxy_session
- # Do we need to flush the session?
- if galaxy_session_requires_flush:
- self.sa_session.add( galaxy_session )
- # FIXME: If prev_session is a proper relation this would not
- # be needed.
- if prev_galaxy_session:
- self.sa_session.add( prev_galaxy_session )
- self.sa_session.flush()
- # If the old session was invalid, get a new history with our new session
- if invalidate_existing_session:
- self.new_history()
-
- def _ensure_logged_in_user( self, environ, session_cookie ):
- # The value of session_cookie can be one of
- # 'galaxysession' or 'galaxycommunitysession'
- # Currently this method does nothing unless session_cookie is 'galaxysession'
- if session_cookie == 'galaxysession' and self.galaxy_session.user is None:
- # TODO: re-engineer to eliminate the use of allowed_paths
- # as maintenance overhead is far too high.
- allowed_paths = (
- url_for( controller='root', action='index' ),
- url_for( controller='root', action='tool_menu' ),
- url_for( controller='root', action='masthead' ),
- url_for( controller='root', action='history' ),
- url_for( controller='user', action='api_keys' ),
- url_for( controller='user', action='create' ),
- url_for( controller='user', action='index' ),
- url_for( controller='user', action='login' ),
- url_for( controller='user', action='logout' ),
- url_for( controller='user', action='manage_user_info' ),
- url_for( controller='user', action='set_default_permissions' ),
- url_for( controller='user', action='reset_password' ),
- url_for( controller='user', action='openid_auth' ),
- url_for( controller='user', action='openid_process' ),
- url_for( controller='user', action='openid_associate' ),
- url_for( controller='library', action='browse' ),
- url_for( controller='history', action='list' ),
- url_for( controller='dataset', action='list' )
- )
- display_as = url_for( controller='root', action='display_as' )
- if self.app.config.ucsc_display_sites and self.request.path == display_as:
- try:
- host = socket.gethostbyaddr( self.environ[ 'REMOTE_ADDR' ] )[0]
- except( socket.error, socket.herror, socket.gaierror, socket.timeout ):
- host = None
- if host in UCSC_SERVERS:
- return
- external_display_path = url_for( controller='', action='display_application' )
- if self.request.path.startswith( external_display_path ):
- request_path_split = self.request.path.split( '/' )
- try:
- if (self.app.datatypes_registry.display_applications.get( request_path_split[-5] )
- and request_path_split[-4] in self.app.datatypes_registry.display_applications.get( request_path_split[-5] ).links
- and request_path_split[-3] != 'None'):
- return
- except IndexError:
- pass
- if self.request.path not in allowed_paths:
- self.response.send_redirect( url_for( controller='root', action='index' ) )
-
- def __create_new_session( self, prev_galaxy_session=None, user_for_new_session=None ):
- """
- Create a new GalaxySession for this request, possibly with a connection
- to a previous session (in `prev_galaxy_session`) and an existing user
- (in `user_for_new_session`).
-
- Caller is responsible for flushing the returned session.
- """
- session_key = self.security.get_new_guid()
- galaxy_session = self.app.model.GalaxySession(
- session_key=session_key,
- is_valid=True,
- remote_host=self.request.remote_host,
- remote_addr=self.request.remote_addr,
- referer=self.request.headers.get( 'Referer', None ) )
- if prev_galaxy_session:
- # Invalidated an existing session for some reason, keep track
- galaxy_session.prev_session_id = prev_galaxy_session.id
- if user_for_new_session:
- # The new session should be associated with the user
- galaxy_session.user = user_for_new_session
- return galaxy_session
-
- def get_or_create_remote_user( self, remote_user_email ):
- """
- Create a remote user with the email remote_user_email and return it
- """
- if not self.app.config.use_remote_user:
- return None
- if getattr( self.app.config, "normalize_remote_user_email", False ):
- remote_user_email = remote_user_email.lower()
- user = self.sa_session.query( self.app.model.User
- ).filter( self.app.model.User.table.c.email==remote_user_email ).first() #noqa
- if user:
- # GVK: June 29, 2009 - This is to correct the behavior of a previous bug where a private
- # role and default user / history permissions were not set for remote users. When a
- # remote user authenticates, we'll look for this information, and if missing, create it.
- if not self.app.security_agent.get_private_user_role( user ):
- self.app.security_agent.create_private_user_role( user )
- if 'webapp' not in self.environ or self.environ['webapp'] != 'tool_shed':
- if not user.default_permissions:
- self.app.security_agent.user_set_default_permissions( user )
- self.app.security_agent.user_set_default_permissions( user, history=True, dataset=True )
- elif user is None:
- username = remote_user_email.split( '@', 1 )[0].lower()
- random.seed()
- user = self.app.model.User( email=remote_user_email )
- user.set_password_cleartext( ''.join( random.sample( string.letters + string.digits, 12 ) ) )
- user.external = True
- # Replace invalid characters in the username
- for char in filter( lambda x: x not in string.ascii_lowercase + string.digits + '-', username ):
- username = username.replace( char, '-' )
- # Find a unique username - user can change it later
- if ( self.sa_session.query( self.app.model.User ).filter_by( username=username ).first() ):
- i = 1
- while ( self.sa_session.query( self.app.model.User ).filter_by( username=(username + '-' + str(i) ) ).first() ):
- i += 1
- username += '-' + str(i)
- user.username = username
- self.sa_session.add( user )
- self.sa_session.flush()
- self.app.security_agent.create_private_user_role( user )
- # We set default user permissions, before we log in and set the default history permissions
- if 'webapp' not in self.environ or self.environ['webapp'] != 'tool_shed':
- self.app.security_agent.user_set_default_permissions( user )
- # self.log_event( "Automatically created account '%s'", user.email )
- return user
-
- def __update_session_cookie( self, name='galaxysession' ):
- """
- Update the session cookie to match the current session.
- """
- self.set_cookie( self.security.encode_guid(self.galaxy_session.session_key ),
- name=name, path=self.app.config.cookie_path )
-
- def handle_user_login( self, user ):
- """
- Login a new user (possibly newly created)
-
- - create a new session
- - associate new session with user
- - if old session had a history and it was not associated with a user, associate it with the new session,
- otherwise associate the current session's history with the user
- - add the disk usage of the current session to the user's total disk usage
- """
- # Set the previous session
- prev_galaxy_session = self.galaxy_session
- prev_galaxy_session.is_valid = False
- # Define a new current_session
- self.galaxy_session = self.__create_new_session( prev_galaxy_session, user )
- if self.webapp.name == 'galaxy':
- cookie_name = 'galaxysession'
- # Associated the current user's last accessed history (if exists) with their new session
- history = None
- try:
- users_last_session = user.galaxy_sessions[0]
- last_accessed = True
- except:
- users_last_session = None
- last_accessed = False
- if (prev_galaxy_session.current_history and not
- prev_galaxy_session.current_history.deleted and
- prev_galaxy_session.current_history.datasets):
- if prev_galaxy_session.current_history.user is None or prev_galaxy_session.current_history.user == user:
- # If the previous galaxy session had a history, associate it with the new
- # session, but only if it didn't belong to a different user.
- history = prev_galaxy_session.current_history
- if prev_galaxy_session.user is None:
- # Increase the user's disk usage by the amount of the previous history's datasets if they didn't already own it.
- for hda in history.datasets:
- user.total_disk_usage += hda.quota_amount( user )
- elif self.galaxy_session.current_history:
- history = self.galaxy_session.current_history
- if (not history and users_last_session and
- users_last_session.current_history and not
- users_last_session.current_history.deleted):
- history = users_last_session.current_history
- elif not history:
- history = self.get_history( create=True )
- if history not in self.galaxy_session.histories:
- self.galaxy_session.add_history( history )
- if history.user is None:
- history.user = user
- self.galaxy_session.current_history = history
- if not last_accessed:
- # Only set default history permissions if current history is not from a previous session
- self.app.security_agent.history_set_default_permissions( history, dataset=True, bypass_manage_permission=True )
- self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session, history ) )
- else:
- cookie_name = 'galaxycommunitysession'
- self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session ) )
- self.sa_session.flush()
- # This method is not called from the Galaxy reports, so the cookie will always be galaxysession
- self.__update_session_cookie( name=cookie_name )
-
- def handle_user_logout( self, logout_all=False ):
- """
- Logout the current user:
- - invalidate the current session
- - create a new session with no user associated
- """
- prev_galaxy_session = self.galaxy_session
- prev_galaxy_session.is_valid = False
- self.galaxy_session = self.__create_new_session( prev_galaxy_session )
- self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session ) )
- galaxy_user_id = prev_galaxy_session.user_id
- if logout_all and galaxy_user_id is not None:
- for other_galaxy_session in self.sa_session.query( self.app.model.GalaxySession
- ).filter( and_( self.app.model.GalaxySession.table.c.user_id==galaxy_user_id, #noqa
- self.app.model.GalaxySession.table.c.is_valid==True, #noqa
- self.app.model.GalaxySession.table.c.id!=prev_galaxy_session.id ) ): #noqa
- other_galaxy_session.is_valid = False
- self.sa_session.add( other_galaxy_session )
- self.sa_session.flush()
- if self.webapp.name == 'galaxy':
- # This method is not called from the Galaxy reports, so the cookie will always be galaxysession
- self.__update_session_cookie( name='galaxysession' )
- elif self.webapp.name == 'tool_shed':
- self.__update_session_cookie( name='galaxycommunitysession' )
-
- def get_galaxy_session( self ):
- """
- Return the current galaxy session
- """
- return self.galaxy_session
-
- def get_history( self, create=False ):
- """
- Load the current history, creating a new one only if there is not
- current history and we're told to create.
- Transactions will not always have an active history (API requests), so
- None is a valid response.
- """
- history = None
- if self.galaxy_session:
- history = self.galaxy_session.current_history
- if not history and util.string_as_bool( create ):
- history = self.new_history()
- return history
-
- def set_history( self, history ):
- if history and not history.deleted:
- self.galaxy_session.current_history = history
- self.sa_session.add( self.galaxy_session )
- self.sa_session.flush()
-
- history = property( get_history, set_history )
-
- def get_or_create_default_history( self ):
- """
- Gets or creates a default history and associates it with the current
- session.
- """
-
- # There must be a user to fetch a default history.
- if not self.galaxy_session.user:
- return self.new_history()
-
- # Look for default history that (a) has default name + is not deleted and
- # (b) has no datasets. If suitable history found, use it; otherwise, create
- # new history.
- unnamed_histories = self.sa_session.query( self.app.model.History ).filter_by(
- user=self.galaxy_session.user,
- name=self.app.model.History.default_name,
- deleted=False )
- default_history = None
- for history in unnamed_histories:
- if len( history.datasets ) == 0:
- # Found suitable default history.
- default_history = history
- break
-
- # Set or create hsitory.
- if default_history:
- history = default_history
- self.set_history( history )
- else:
- history = self.new_history()
-
- return history
-
- def new_history( self, name=None ):
- """
- Create a new history and associate it with the current session and
- its associated user (if set).
- """
- # Create new history
- history = self.app.model.History()
- if name:
- history.name = name
- # Associate with session
- history.add_galaxy_session( self.galaxy_session )
- # Make it the session's current history
- self.galaxy_session.current_history = history
- # Associate with user
- if self.galaxy_session.user:
- history.user = self.galaxy_session.user
- # Track genome_build with history
- history.genome_build = self.app.genome_builds.default_value
- # Set the user's default history permissions
- self.app.security_agent.history_set_default_permissions( history )
- # Save
- self.sa_session.add_all( ( self.galaxy_session, history ) )
- self.sa_session.flush()
- return history
-
- @base.lazy_property
- def template_context( self ):
- return dict()
-
- def make_form_data( self, name, **kwargs ):
- rval = self.template_context[name] = FormData()
- rval.values.update( kwargs )
- return rval
-
- def set_message( self, message, type=None ):
- """
- Convenience method for setting the 'message' and 'message_type'
- element of the template context.
- """
- self.template_context['message'] = message
- if type:
- self.template_context['status'] = type
-
- def get_message( self ):
- """
- Convenience method for getting the 'message' element of the template
- context.
- """
- return self.template_context['message']
-
- def show_message( self, message, type='info', refresh_frames=[], cont=None, use_panels=False, active_view="" ):
- """
- Convenience method for displaying a simple page with a single message.
-
- `type`: one of "error", "warning", "info", or "done"; determines the
- type of dialog box and icon displayed with the message
-
- `refresh_frames`: names of frames in the interface that should be
- refreshed when the message is displayed
- """
- return self.fill_template( "message.mako", status=type, message=message, refresh_frames=refresh_frames, cont=cont, use_panels=use_panels, active_view=active_view )
-
- def show_error_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
- """
- Convenience method for displaying an error message. See `show_message`.
- """
- return self.show_message( message, 'error', refresh_frames, use_panels=use_panels, active_view=active_view )
-
- def show_ok_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
- """
- Convenience method for displaying an ok message. See `show_message`.
- """
- return self.show_message( message, 'done', refresh_frames, use_panels=use_panels, active_view=active_view )
-
- def show_warn_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
- """
- Convenience method for displaying an warn message. See `show_message`.
- """
- return self.show_message( message, 'warning', refresh_frames, use_panels=use_panels, active_view=active_view )
-
- def show_form( self, form, header=None, template="form.mako", use_panels=False, active_view="" ):
- """
- Convenience method for displaying a simple page with a single HTML
- form.
- """
- return self.fill_template( template, form=form, header=header,
- use_panels=( form.use_panels or use_panels ),
- active_view=active_view )
-
- def fill_template(self, filename, **kwargs):
- """
- Fill in a template, putting any keyword arguments on the context.
- """
- # call get_user so we can invalidate sessions from external users,
- # if external auth has been disabled.
- self.get_user()
- if filename.endswith( ".mako" ):
- return self.fill_template_mako( filename, **kwargs )
- else:
- template = Template( file=os.path.join(self.app.config.template_path, filename),
- searchList=[kwargs, self.template_context, dict(caller=self, t=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app)] )
- return str( template )
-
- def fill_template_mako( self, filename, template_lookup=None, **kwargs ):
- template_lookup = template_lookup or self.webapp.mako_template_lookup
- template = template_lookup.get_template( filename )
- template.output_encoding = 'utf-8'
-
- data = dict( caller=self, t=self, trans=self, h=helpers, util=util,
- request=self.request, response=self.response, app=self.app )
- data.update( self.template_context )
- data.update( kwargs )
- return template.render( **data )
-
- def stream_template_mako( self, filename, **kwargs ):
- template = self.webapp.mako_template_lookup.get_template( filename )
- template.output_encoding = 'utf-8'
- data = dict( caller=self, t=self, trans=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app )
- data.update( self.template_context )
- data.update( kwargs )
-
- def render( environ, start_response ):
- response_write = start_response( self.response.wsgi_status(), self.response.wsgi_headeritems() )
-
- class StreamBuffer( object ):
- def write( self, d ):
- response_write( d.encode( 'utf-8' ) )
- buffer = StreamBuffer()
- context = mako.runtime.Context( buffer, **data )
- template.render_context( context )
- return []
- return render
-
- def fill_template_string(self, template_string, context=None, **kwargs):
- """
- Fill in a template, putting any keyword arguments on the context.
- """
- template = Template( source=template_string,
- searchList=[context or kwargs, dict(caller=self)] )
- return str(template)
-
-
diff -r 360d9d7839d74f01a2da5b35fb1be10464956112 -r a0c16c8930b76cfe98574b2e8b3311f7b2504361 lib/galaxy/web/framework/webapp.py
--- a/lib/galaxy/web/framework/webapp.py
+++ b/lib/galaxy/web/framework/webapp.py
@@ -1,16 +1,40 @@
+"""
+"""
import inspect
import os
+import hashlib
+import random
+import socket
+import string
+import time
+from Cookie import CookieError
from galaxy import eggs
eggs.require( "Cheetah" )
from Cheetah.Template import Template
eggs.require( "Mako" )
+import mako.runtime
import mako.lookup
+# pytz is used by Babel.
+eggs.require( "pytz" )
+eggs.require( "Babel" )
+from babel.support import Translations
+from babel import Locale
+eggs.require( "SQLAlchemy >= 0.4" )
+from sqlalchemy import and_
+from sqlalchemy.orm.exc import NoResultFound
from galaxy.exceptions import MessageException
+
+from galaxy import util
+from galaxy.util import asbool
+from galaxy.util import safe_str_cmp
from galaxy.util.backports.importlib import import_module
from galaxy.util.sanitize_html import sanitize_html
+
+from galaxy.managers import context
from galaxy.web.framework import base
+from galaxy.web.framework import helpers
from galaxy.web.framework import transaction
from galaxy.web.framework import formbuilder
@@ -18,8 +42,27 @@
log = logging.getLogger( __name__ )
+UCSC_SERVERS = (
+ 'hgw1.cse.ucsc.edu',
+ 'hgw2.cse.ucsc.edu',
+ 'hgw3.cse.ucsc.edu',
+ 'hgw4.cse.ucsc.edu',
+ 'hgw5.cse.ucsc.edu',
+ 'hgw6.cse.ucsc.edu',
+ 'hgw7.cse.ucsc.edu',
+ 'hgw8.cse.ucsc.edu',
+)
+
+
class WebApplication( base.WebApplication ):
-
+ """
+ A web application that:
+ * adds API and UI controllers by scanning given directories and
+ importing all modules found there.
+ * has a security object.
+ * builds mako template lookups.
+ * generates GalaxyWebTransactions.
+ """
def __init__( self, galaxy_app, session_cookie='galaxysession', name=None ):
self.name = name
base.WebApplication.__init__( self )
@@ -54,7 +97,7 @@
return base.WebApplication.make_body_iterable( self, trans, body )
def transaction_chooser( self, environ, galaxy_app, session_cookie ):
- return transaction.GalaxyWebTransaction( environ, galaxy_app, self, session_cookie )
+ return GalaxyWebTransaction( environ, galaxy_app, self, session_cookie )
def add_ui_controllers( self, package_name, app ):
"""
@@ -113,3 +156,656 @@
really just used to stub out actual controllers for routes testing.
"""
return T( app )
+
+
+class GalaxyWebTransaction( base.DefaultWebTransaction,
+ context.ProvidesAppContext, context.ProvidesUserContext, context.ProvidesHistoryContext ):
+ """
+ Encapsulates web transaction specific state for the Galaxy application
+ (specifically the user's "cookie" session and history)
+ """
+
+ def __init__( self, environ, app, webapp, session_cookie=None):
+ self.app = app
+ self.webapp = webapp
+ self.security = webapp.security
+ base.DefaultWebTransaction.__init__( self, environ )
+ self.setup_i18n()
+ self.expunge_all()
+ self.debug = asbool( self.app.config.get( 'debug', False ) )
+ # Flag indicating whether we are in workflow building mode (means
+ # that the current history should not be used for parameter values
+ # and such).
+ self.workflow_building_mode = False
+ # Flag indicating whether this is an API call and the API key user is an administrator
+ self.api_inherit_admin = False
+ self.__user = None
+ self.galaxy_session = None
+ self.error_message = None
+
+ if self.environ.get('is_api_request', False):
+ # With API requests, if there's a key, use it and associate the
+ # user with the transaction.
+ # If not, check for an active session but do not create one.
+ # If an error message is set here, it's sent back using
+ # trans.show_error in the response -- in expose_api.
+ self.error_message = self._authenticate_api( session_cookie )
+ elif self.app.name == "reports":
+ self.galaxy_session = None
+ else:
+ # This is a web request, get or create session.
+ self._ensure_valid_session( session_cookie )
+ if self.galaxy_session:
+ # When we've authenticated by session, we have to check the
+ # following.
+ # Prevent deleted users from accessing Galaxy
+ if self.app.config.use_remote_user and self.galaxy_session.user.deleted:
+ self.response.send_redirect( url_for( '/static/user_disabled.html' ) )
+ if self.app.config.require_login:
+ self._ensure_logged_in_user( environ, session_cookie )
+
+ def setup_i18n( self ):
+ locales = []
+ if 'HTTP_ACCEPT_LANGUAGE' in self.environ:
+ # locales looks something like: ['en', 'en-us;q=0.7', 'ja;q=0.3']
+ client_locales = self.environ['HTTP_ACCEPT_LANGUAGE'].split( ',' )
+ for locale in client_locales:
+ try:
+ locales.append( Locale.parse( locale.split( ';' )[0].strip(), sep='-' ).language )
+ except Exception, e:
+ log.debug( "Error parsing locale '%s'. %s: %s", locale, type( e ), e )
+ if not locales:
+ # Default to English
+ locales = 'en'
+ t = Translations.load( dirname='locale', locales=locales, domain='ginga' )
+ self.template_context.update( dict( _=t.ugettext, n_=t.ugettext, N_=t.ungettext ) )
+
+ def get_user( self ):
+ """Return the current user if logged in or None."""
+ if self.galaxy_session:
+ return self.galaxy_session.user
+ else:
+ return self.__user
+
+ def set_user( self, user ):
+ """Set the current user."""
+ if self.galaxy_session:
+ self.galaxy_session.user = user
+ self.sa_session.add( self.galaxy_session )
+ self.sa_session.flush()
+ self.__user = user
+
+ user = property( get_user, set_user )
+
+ def get_cookie( self, name='galaxysession' ):
+ """Convenience method for getting a session cookie"""
+ try:
+ # If we've changed the cookie during the request return the new value
+ if name in self.response.cookies:
+ return self.response.cookies[name].value
+ else:
+ return self.request.cookies[name].value
+ except:
+ return None
+
+ def set_cookie( self, value, name='galaxysession', path='/', age=90, version='1' ):
+ """Convenience method for setting a session cookie"""
+ # The galaxysession cookie value must be a high entropy 128 bit random number encrypted
+ # using a server secret key. Any other value is invalid and could pose security issues.
+ self.response.cookies[name] = value
+ self.response.cookies[name]['path'] = path
+ self.response.cookies[name]['max-age'] = 3600 * 24 * age # 90 days
+ tstamp = time.localtime( time.time() + 3600 * 24 * age )
+ self.response.cookies[name]['expires'] = time.strftime( '%a, %d-%b-%Y %H:%M:%S GMT', tstamp )
+ self.response.cookies[name]['version'] = version
+ try:
+ self.response.cookies[name]['httponly'] = True
+ except CookieError, e:
+ log.warning( "Error setting httponly attribute in cookie '%s': %s" % ( name, e ) )
+
+ def _authenticate_api( self, session_cookie ):
+ """
+ Authenticate for the API via key or session (if available).
+ """
+ api_key = self.request.params.get('key', None)
+ secure_id = self.get_cookie( name=session_cookie )
+ api_key_supplied = self.environ.get('is_api_request', False) and api_key
+ if api_key_supplied and self._check_master_api_key( api_key ):
+ self.api_inherit_admin = True
+ log.info( "Session authenticated using Galaxy master api key" )
+ self.user = None
+ self.galaxy_session = None
+ elif api_key_supplied:
+ # Sessionless API transaction, we just need to associate a user.
+ try:
+ provided_key = self.sa_session.query( self.app.model.APIKeys ).filter( self.app.model.APIKeys.table.c.key == api_key ).one()
+ except NoResultFound:
+ return 'Provided API key is not valid.'
+ if provided_key.user.deleted:
+ return 'User account is deactivated, please contact an administrator.'
+ newest_key = provided_key.user.api_keys[0]
+ if newest_key.key != provided_key.key:
+ return 'Provided API key has expired.'
+ self.set_user( provided_key.user )
+ elif secure_id:
+ # API authentication via active session
+ # Associate user using existing session
+ self._ensure_valid_session( session_cookie )
+ else:
+ # Anonymous API interaction -- anything but @expose_api_anonymous will fail past here.
+ self.user = None
+ self.galaxy_session = None
+
+ def _check_master_api_key( self, api_key ):
+ master_api_key = getattr( self.app.config, 'master_api_key', None )
+ if not master_api_key:
+ return False
+ # Hash keys to make them the same size, so we can do safe comparison.
+ master_hash = hashlib.sha256( master_api_key ).hexdigest()
+ provided_hash = hashlib.sha256( api_key ).hexdigest()
+ return safe_str_cmp( master_hash, provided_hash )
+
+ def _ensure_valid_session( self, session_cookie, create=True):
+ """
+ Ensure that a valid Galaxy session exists and is available as
+ trans.session (part of initialization)
+
+ Support for universe_session and universe_user cookies has been
+ removed as of 31 Oct 2008.
+ """
+ # Try to load an existing session
+ secure_id = self.get_cookie( name=session_cookie )
+ galaxy_session = None
+ prev_galaxy_session = None
+ user_for_new_session = None
+ invalidate_existing_session = False
+ # Track whether the session has changed so we can avoid calling flush
+ # in the most common case (session exists and is valid).
+ galaxy_session_requires_flush = False
+ if secure_id:
+ # Decode the cookie value to get the session_key
+ session_key = self.security.decode_guid( secure_id )
+ try:
+ # Make sure we have a valid UTF-8 string
+ session_key = session_key.encode( 'utf8' )
+ except UnicodeDecodeError:
+ # We'll end up creating a new galaxy_session
+ session_key = None
+ if session_key:
+ # Retrieve the galaxy_session id via the unique session_key
+ galaxy_session = self.sa_session.query( self.app.model.GalaxySession ) \
+ .filter( and_( self.app.model.GalaxySession.table.c.session_key==session_key, #noqa
+ self.app.model.GalaxySession.table.c.is_valid==True ) ).first() #noqa
+ # If remote user is in use it can invalidate the session and in some
+ # cases won't have a cookie set above, so we need to to check some
+ # things now.
+ if self.app.config.use_remote_user:
+ # If this is an api request, and they've passed a key, we let this go.
+ assert self.app.config.remote_user_header in self.environ, \
+ "use_remote_user is set but %s header was not provided" % self.app.config.remote_user_header
+ remote_user_email = self.environ[ self.app.config.remote_user_header ]
+ if getattr( self.app.config, "normalize_remote_user_email", False ):
+ remote_user_email = remote_user_email.lower()
+ if galaxy_session:
+ # An existing session, make sure correct association exists
+ if galaxy_session.user is None:
+ # No user, associate
+ galaxy_session.user = self.get_or_create_remote_user( remote_user_email )
+ galaxy_session_requires_flush = True
+ elif ((galaxy_session.user.email != remote_user_email) and
+ ((not self.app.config.allow_user_impersonation) or
+ (remote_user_email not in self.app.config.admin_users_list))):
+ # Session exists but is not associated with the correct
+ # remote user, and the currently set remote_user is not a
+ # potentially impersonating admin.
+ invalidate_existing_session = True
+ user_for_new_session = self.get_or_create_remote_user( remote_user_email )
+ log.warning( "User logged in as '%s' externally, but has a cookie as '%s' invalidating session",
+ remote_user_email, galaxy_session.user.email )
+ else:
+ # No session exists, get/create user for new session
+ user_for_new_session = self.get_or_create_remote_user( remote_user_email )
+ else:
+ if galaxy_session is not None and galaxy_session.user and galaxy_session.user.external:
+ # Remote user support is not enabled, but there is an existing
+ # session with an external user, invalidate
+ invalidate_existing_session = True
+ log.warning( "User '%s' is an external user with an existing session, invalidating session since external auth is disabled",
+ galaxy_session.user.email )
+ elif galaxy_session is not None and galaxy_session.user is not None and galaxy_session.user.deleted:
+ invalidate_existing_session = True
+ log.warning( "User '%s' is marked deleted, invalidating session" % galaxy_session.user.email )
+ # Do we need to invalidate the session for some reason?
+ if invalidate_existing_session:
+ prev_galaxy_session = galaxy_session
+ prev_galaxy_session.is_valid = False
+ galaxy_session = None
+ # No relevant cookies, or couldn't find, or invalid, so create a new session
+ if galaxy_session is None:
+ galaxy_session = self.__create_new_session( prev_galaxy_session, user_for_new_session )
+ galaxy_session_requires_flush = True
+ self.galaxy_session = galaxy_session
+ self.__update_session_cookie( name=session_cookie )
+ else:
+ self.galaxy_session = galaxy_session
+ # Do we need to flush the session?
+ if galaxy_session_requires_flush:
+ self.sa_session.add( galaxy_session )
+ # FIXME: If prev_session is a proper relation this would not
+ # be needed.
+ if prev_galaxy_session:
+ self.sa_session.add( prev_galaxy_session )
+ self.sa_session.flush()
+ # If the old session was invalid, get a new history with our new session
+ if invalidate_existing_session:
+ self.new_history()
+
+ def _ensure_logged_in_user( self, environ, session_cookie ):
+ # The value of session_cookie can be one of
+ # 'galaxysession' or 'galaxycommunitysession'
+ # Currently this method does nothing unless session_cookie is 'galaxysession'
+ if session_cookie == 'galaxysession' and self.galaxy_session.user is None:
+ # TODO: re-engineer to eliminate the use of allowed_paths
+ # as maintenance overhead is far too high.
+ allowed_paths = (
+ url_for( controller='root', action='index' ),
+ url_for( controller='root', action='tool_menu' ),
+ url_for( controller='root', action='masthead' ),
+ url_for( controller='root', action='history' ),
+ url_for( controller='user', action='api_keys' ),
+ url_for( controller='user', action='create' ),
+ url_for( controller='user', action='index' ),
+ url_for( controller='user', action='login' ),
+ url_for( controller='user', action='logout' ),
+ url_for( controller='user', action='manage_user_info' ),
+ url_for( controller='user', action='set_default_permissions' ),
+ url_for( controller='user', action='reset_password' ),
+ url_for( controller='user', action='openid_auth' ),
+ url_for( controller='user', action='openid_process' ),
+ url_for( controller='user', action='openid_associate' ),
+ url_for( controller='library', action='browse' ),
+ url_for( controller='history', action='list' ),
+ url_for( controller='dataset', action='list' )
+ )
+ display_as = url_for( controller='root', action='display_as' )
+ if self.app.config.ucsc_display_sites and self.request.path == display_as:
+ try:
+ host = socket.gethostbyaddr( self.environ[ 'REMOTE_ADDR' ] )[0]
+ except( socket.error, socket.herror, socket.gaierror, socket.timeout ):
+ host = None
+ if host in UCSC_SERVERS:
+ return
+ external_display_path = url_for( controller='', action='display_application' )
+ if self.request.path.startswith( external_display_path ):
+ request_path_split = self.request.path.split( '/' )
+ try:
+ if (self.app.datatypes_registry.display_applications.get( request_path_split[-5] )
+ and request_path_split[-4] in self.app.datatypes_registry.display_applications.get( request_path_split[-5] ).links
+ and request_path_split[-3] != 'None'):
+ return
+ except IndexError:
+ pass
+ if self.request.path not in allowed_paths:
+ self.response.send_redirect( url_for( controller='root', action='index' ) )
+
+ def __create_new_session( self, prev_galaxy_session=None, user_for_new_session=None ):
+ """
+ Create a new GalaxySession for this request, possibly with a connection
+ to a previous session (in `prev_galaxy_session`) and an existing user
+ (in `user_for_new_session`).
+
+ Caller is responsible for flushing the returned session.
+ """
+ session_key = self.security.get_new_guid()
+ galaxy_session = self.app.model.GalaxySession(
+ session_key=session_key,
+ is_valid=True,
+ remote_host=self.request.remote_host,
+ remote_addr=self.request.remote_addr,
+ referer=self.request.headers.get( 'Referer', None ) )
+ if prev_galaxy_session:
+ # Invalidated an existing session for some reason, keep track
+ galaxy_session.prev_session_id = prev_galaxy_session.id
+ if user_for_new_session:
+ # The new session should be associated with the user
+ galaxy_session.user = user_for_new_session
+ return galaxy_session
+
+ def get_or_create_remote_user( self, remote_user_email ):
+ """
+ Create a remote user with the email remote_user_email and return it
+ """
+ if not self.app.config.use_remote_user:
+ return None
+ if getattr( self.app.config, "normalize_remote_user_email", False ):
+ remote_user_email = remote_user_email.lower()
+ user = self.sa_session.query( self.app.model.User
+ ).filter( self.app.model.User.table.c.email==remote_user_email ).first() #noqa
+ if user:
+ # GVK: June 29, 2009 - This is to correct the behavior of a previous bug where a private
+ # role and default user / history permissions were not set for remote users. When a
+ # remote user authenticates, we'll look for this information, and if missing, create it.
+ if not self.app.security_agent.get_private_user_role( user ):
+ self.app.security_agent.create_private_user_role( user )
+ if 'webapp' not in self.environ or self.environ['webapp'] != 'tool_shed':
+ if not user.default_permissions:
+ self.app.security_agent.user_set_default_permissions( user )
+ self.app.security_agent.user_set_default_permissions( user, history=True, dataset=True )
+ elif user is None:
+ username = remote_user_email.split( '@', 1 )[0].lower()
+ random.seed()
+ user = self.app.model.User( email=remote_user_email )
+ user.set_password_cleartext( ''.join( random.sample( string.letters + string.digits, 12 ) ) )
+ user.external = True
+ # Replace invalid characters in the username
+ for char in filter( lambda x: x not in string.ascii_lowercase + string.digits + '-', username ):
+ username = username.replace( char, '-' )
+ # Find a unique username - user can change it later
+ if ( self.sa_session.query( self.app.model.User ).filter_by( username=username ).first() ):
+ i = 1
+ while ( self.sa_session.query( self.app.model.User ).filter_by( username=(username + '-' + str(i) ) ).first() ):
+ i += 1
+ username += '-' + str(i)
+ user.username = username
+ self.sa_session.add( user )
+ self.sa_session.flush()
+ self.app.security_agent.create_private_user_role( user )
+ # We set default user permissions, before we log in and set the default history permissions
+ if 'webapp' not in self.environ or self.environ['webapp'] != 'tool_shed':
+ self.app.security_agent.user_set_default_permissions( user )
+ # self.log_event( "Automatically created account '%s'", user.email )
+ return user
+
+ def __update_session_cookie( self, name='galaxysession' ):
+ """
+ Update the session cookie to match the current session.
+ """
+ self.set_cookie( self.security.encode_guid(self.galaxy_session.session_key ),
+ name=name, path=self.app.config.cookie_path )
+
+ def handle_user_login( self, user ):
+ """
+ Login a new user (possibly newly created)
+
+ - create a new session
+ - associate new session with user
+ - if old session had a history and it was not associated with a user, associate it with the new session,
+ otherwise associate the current session's history with the user
+ - add the disk usage of the current session to the user's total disk usage
+ """
+ # Set the previous session
+ prev_galaxy_session = self.galaxy_session
+ prev_galaxy_session.is_valid = False
+ # Define a new current_session
+ self.galaxy_session = self.__create_new_session( prev_galaxy_session, user )
+ if self.webapp.name == 'galaxy':
+ cookie_name = 'galaxysession'
+ # Associated the current user's last accessed history (if exists) with their new session
+ history = None
+ try:
+ users_last_session = user.galaxy_sessions[0]
+ last_accessed = True
+ except:
+ users_last_session = None
+ last_accessed = False
+ if (prev_galaxy_session.current_history and not
+ prev_galaxy_session.current_history.deleted and
+ prev_galaxy_session.current_history.datasets):
+ if prev_galaxy_session.current_history.user is None or prev_galaxy_session.current_history.user == user:
+ # If the previous galaxy session had a history, associate it with the new
+ # session, but only if it didn't belong to a different user.
+ history = prev_galaxy_session.current_history
+ if prev_galaxy_session.user is None:
+ # Increase the user's disk usage by the amount of the previous history's datasets if they didn't already own it.
+ for hda in history.datasets:
+ user.total_disk_usage += hda.quota_amount( user )
+ elif self.galaxy_session.current_history:
+ history = self.galaxy_session.current_history
+ if (not history and users_last_session and
+ users_last_session.current_history and not
+ users_last_session.current_history.deleted):
+ history = users_last_session.current_history
+ elif not history:
+ history = self.get_history( create=True )
+ if history not in self.galaxy_session.histories:
+ self.galaxy_session.add_history( history )
+ if history.user is None:
+ history.user = user
+ self.galaxy_session.current_history = history
+ if not last_accessed:
+ # Only set default history permissions if current history is not from a previous session
+ self.app.security_agent.history_set_default_permissions( history, dataset=True, bypass_manage_permission=True )
+ self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session, history ) )
+ else:
+ cookie_name = 'galaxycommunitysession'
+ self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session ) )
+ self.sa_session.flush()
+ # This method is not called from the Galaxy reports, so the cookie will always be galaxysession
+ self.__update_session_cookie( name=cookie_name )
+
+ def handle_user_logout( self, logout_all=False ):
+ """
+ Logout the current user:
+ - invalidate the current session
+ - create a new session with no user associated
+ """
+ prev_galaxy_session = self.galaxy_session
+ prev_galaxy_session.is_valid = False
+ self.galaxy_session = self.__create_new_session( prev_galaxy_session )
+ self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session ) )
+ galaxy_user_id = prev_galaxy_session.user_id
+ if logout_all and galaxy_user_id is not None:
+ for other_galaxy_session in self.sa_session.query( self.app.model.GalaxySession
+ ).filter( and_( self.app.model.GalaxySession.table.c.user_id==galaxy_user_id, #noqa
+ self.app.model.GalaxySession.table.c.is_valid==True, #noqa
+ self.app.model.GalaxySession.table.c.id!=prev_galaxy_session.id ) ): #noqa
+ other_galaxy_session.is_valid = False
+ self.sa_session.add( other_galaxy_session )
+ self.sa_session.flush()
+ if self.webapp.name == 'galaxy':
+ # This method is not called from the Galaxy reports, so the cookie will always be galaxysession
+ self.__update_session_cookie( name='galaxysession' )
+ elif self.webapp.name == 'tool_shed':
+ self.__update_session_cookie( name='galaxycommunitysession' )
+
+ def get_galaxy_session( self ):
+ """
+ Return the current galaxy session
+ """
+ return self.galaxy_session
+
+ def get_history( self, create=False ):
+ """
+ Load the current history, creating a new one only if there is not
+ current history and we're told to create.
+ Transactions will not always have an active history (API requests), so
+ None is a valid response.
+ """
+ history = None
+ if self.galaxy_session:
+ history = self.galaxy_session.current_history
+ if not history and util.string_as_bool( create ):
+ history = self.new_history()
+ return history
+
+ def set_history( self, history ):
+ if history and not history.deleted:
+ self.galaxy_session.current_history = history
+ self.sa_session.add( self.galaxy_session )
+ self.sa_session.flush()
+
+ history = property( get_history, set_history )
+
+ def get_or_create_default_history( self ):
+ """
+ Gets or creates a default history and associates it with the current
+ session.
+ """
+
+ # There must be a user to fetch a default history.
+ if not self.galaxy_session.user:
+ return self.new_history()
+
+ # Look for default history that (a) has default name + is not deleted and
+ # (b) has no datasets. If suitable history found, use it; otherwise, create
+ # new history.
+ unnamed_histories = self.sa_session.query( self.app.model.History ).filter_by(
+ user=self.galaxy_session.user,
+ name=self.app.model.History.default_name,
+ deleted=False )
+ default_history = None
+ for history in unnamed_histories:
+ if len( history.datasets ) == 0:
+ # Found suitable default history.
+ default_history = history
+ break
+
+ # Set or create hsitory.
+ if default_history:
+ history = default_history
+ self.set_history( history )
+ else:
+ history = self.new_history()
+
+ return history
+
+ def new_history( self, name=None ):
+ """
+ Create a new history and associate it with the current session and
+ its associated user (if set).
+ """
+ # Create new history
+ history = self.app.model.History()
+ if name:
+ history.name = name
+ # Associate with session
+ history.add_galaxy_session( self.galaxy_session )
+ # Make it the session's current history
+ self.galaxy_session.current_history = history
+ # Associate with user
+ if self.galaxy_session.user:
+ history.user = self.galaxy_session.user
+ # Track genome_build with history
+ history.genome_build = self.app.genome_builds.default_value
+ # Set the user's default history permissions
+ self.app.security_agent.history_set_default_permissions( history )
+ # Save
+ self.sa_session.add_all( ( self.galaxy_session, history ) )
+ self.sa_session.flush()
+ return history
+
+ @base.lazy_property
+ def template_context( self ):
+ return dict()
+
+ def make_form_data( self, name, **kwargs ):
+ rval = self.template_context[name] = FormData()
+ rval.values.update( kwargs )
+ return rval
+
+ def set_message( self, message, type=None ):
+ """
+ Convenience method for setting the 'message' and 'message_type'
+ element of the template context.
+ """
+ self.template_context['message'] = message
+ if type:
+ self.template_context['status'] = type
+
+ def get_message( self ):
+ """
+ Convenience method for getting the 'message' element of the template
+ context.
+ """
+ return self.template_context['message']
+
+ def show_message( self, message, type='info', refresh_frames=[], cont=None, use_panels=False, active_view="" ):
+ """
+ Convenience method for displaying a simple page with a single message.
+
+ `type`: one of "error", "warning", "info", or "done"; determines the
+ type of dialog box and icon displayed with the message
+
+ `refresh_frames`: names of frames in the interface that should be
+ refreshed when the message is displayed
+ """
+ return self.fill_template( "message.mako", status=type, message=message, refresh_frames=refresh_frames, cont=cont, use_panels=use_panels, active_view=active_view )
+
+ def show_error_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
+ """
+ Convenience method for displaying an error message. See `show_message`.
+ """
+ return self.show_message( message, 'error', refresh_frames, use_panels=use_panels, active_view=active_view )
+
+ def show_ok_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
+ """
+ Convenience method for displaying an ok message. See `show_message`.
+ """
+ return self.show_message( message, 'done', refresh_frames, use_panels=use_panels, active_view=active_view )
+
+ def show_warn_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
+ """
+ Convenience method for displaying an warn message. See `show_message`.
+ """
+ return self.show_message( message, 'warning', refresh_frames, use_panels=use_panels, active_view=active_view )
+
+ def show_form( self, form, header=None, template="form.mako", use_panels=False, active_view="" ):
+ """
+ Convenience method for displaying a simple page with a single HTML
+ form.
+ """
+ return self.fill_template( template, form=form, header=header,
+ use_panels=( form.use_panels or use_panels ),
+ active_view=active_view )
+
+ def fill_template(self, filename, **kwargs):
+ """
+ Fill in a template, putting any keyword arguments on the context.
+ """
+ # call get_user so we can invalidate sessions from external users,
+ # if external auth has been disabled.
+ self.get_user()
+ if filename.endswith( ".mako" ):
+ return self.fill_template_mako( filename, **kwargs )
+ else:
+ template = Template( file=os.path.join(self.app.config.template_path, filename),
+ searchList=[kwargs, self.template_context, dict(caller=self, t=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app)] )
+ return str( template )
+
+ def fill_template_mako( self, filename, template_lookup=None, **kwargs ):
+ template_lookup = template_lookup or self.webapp.mako_template_lookup
+ template = template_lookup.get_template( filename )
+ template.output_encoding = 'utf-8'
+
+ data = dict( caller=self, t=self, trans=self, h=helpers, util=util,
+ request=self.request, response=self.response, app=self.app )
+ data.update( self.template_context )
+ data.update( kwargs )
+ return template.render( **data )
+
+ def stream_template_mako( self, filename, **kwargs ):
+ template = self.webapp.mako_template_lookup.get_template( filename )
+ template.output_encoding = 'utf-8'
+ data = dict( caller=self, t=self, trans=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app )
+ data.update( self.template_context )
+ data.update( kwargs )
+
+ def render( environ, start_response ):
+ response_write = start_response( self.response.wsgi_status(), self.response.wsgi_headeritems() )
+
+ class StreamBuffer( object ):
+ def write( self, d ):
+ response_write( d.encode( 'utf-8' ) )
+ buffer = StreamBuffer()
+ context = mako.runtime.Context( buffer, **data )
+ template.render_context( context )
+ return []
+ return render
+
+ def fill_template_string(self, template_string, context=None, **kwargs):
+ """
+ Fill in a template, putting any keyword arguments on the context.
+ """
+ template = Template( source=template_string,
+ searchList=[context or kwargs, dict(caller=self)] )
+ return str(template)
https://bitbucket.org/galaxy/galaxy-central/commits/d66fefbc6bd6/
Changeset: d66fefbc6bd6
User: carlfeberhard
Date: 2014-09-03 17:22:29
Summary: merge central
Affected #: 1 file
diff -r a0c16c8930b76cfe98574b2e8b3311f7b2504361 -r d66fefbc6bd67201f8833e7c5d5a881d4830951f lib/galaxy/managers/folders.py
--- a/lib/galaxy/managers/folders.py
+++ b/lib/galaxy/managers/folders.py
@@ -83,7 +83,7 @@
Check whether the folder is accessible to current user.
By default every folder is accessible (contents have their own permissions).
"""
- return True
+ return folder
def get_folder_dict( self, trans, folder ):
"""
@@ -156,6 +156,19 @@
add_library_item_role_list = [ ( add_role.name, trans.security.encode_id( add_role.id ) ) for add_role in add_roles ]
return dict( modify_folder_role_list=modify_folder_role_list, manage_folder_role_list=manage_folder_role_list, add_library_item_role_list=add_library_item_role_list )
+ def can_add_item( self, trans, folder ):
+ """
+ Return true if the user has permissions to add item to the given folder.
+ """
+ if trans.user_is_admin:
+ return True
+ current_user_roles = trans.get_current_user_roles()
+ add_roles = set( trans.app.security_agent.get_roles_for_action( folder, trans.app.security_agent.permitted_actions.LIBRARY_ADD ) )
+ for role in current_user_roles:
+ if role in add_roles:
+ return True
+ return False
+
def cut_the_prefix( self, encoded_folder_id ):
"""
Remove the prefix from the encoded folder id.
https://bitbucket.org/galaxy/galaxy-central/commits/5bc83c22c468/
Changeset: 5bc83c22c468
User: carlfeberhard
Date: 2014-09-03 17:44:25
Summary: Add managers/context.py, remove import of transaction
Affected #: 1 file
diff -r d66fefbc6bd67201f8833e7c5d5a881d4830951f -r 5bc83c22c4685bae000f6ff7bd2b43a695138022 lib/galaxy/web/framework/webapp.py
--- a/lib/galaxy/web/framework/webapp.py
+++ b/lib/galaxy/web/framework/webapp.py
@@ -35,7 +35,6 @@
from galaxy.managers import context
from galaxy.web.framework import base
from galaxy.web.framework import helpers
-from galaxy.web.framework import transaction
from galaxy.web.framework import formbuilder
import logging
https://bitbucket.org/galaxy/galaxy-central/commits/efd4b01d8ae6/
Changeset: efd4b01d8ae6
User: carlfeberhard
Date: 2014-09-03 17:46:38
Summary: Fix to 5bc83c2
Affected #: 1 file
diff -r 5bc83c22c4685bae000f6ff7bd2b43a695138022 -r efd4b01d8ae64f6a7d25011295a919fea3324f1d lib/galaxy/managers/context.py
--- /dev/null
+++ b/lib/galaxy/managers/context.py
@@ -0,0 +1,179 @@
+"""
+Mixins for transaction-like objects.
+"""
+
+import os
+
+from galaxy.util.json import to_json_string
+from galaxy.util import bunch
+
+class ProvidesAppContext( object ):
+ """ For transaction-like objects to provide Galaxy convience layer for
+ database and event handling.
+
+ Mixed in class must provide `app` property.
+ """
+
+ def log_action( self, user=None, action=None, context=None, params=None):
+ """
+ Application-level logging of user actions.
+ """
+ if self.app.config.log_actions:
+ action = self.app.model.UserAction(action=action, context=context, params=unicode( to_json_string( params ) ) )
+ try:
+ if user:
+ action.user = user
+ else:
+ action.user = self.user
+ except:
+ action.user = None
+ try:
+ action.session_id = self.galaxy_session.id
+ except:
+ action.session_id = None
+ self.sa_session.add( action )
+ self.sa_session.flush()
+
+ def log_event( self, message, tool_id=None, **kwargs ):
+ """
+ Application level logging. Still needs fleshing out (log levels and such)
+ Logging events is a config setting - if False, do not log.
+ """
+ if self.app.config.log_events:
+ event = self.app.model.Event()
+ event.tool_id = tool_id
+ try:
+ event.message = message % kwargs
+ except:
+ event.message = message
+ try:
+ event.history = self.get_history()
+ except:
+ event.history = None
+ try:
+ event.history_id = self.history.id
+ except:
+ event.history_id = None
+ try:
+ event.user = self.user
+ except:
+ event.user = None
+ try:
+ event.session_id = self.galaxy_session.id
+ except:
+ event.session_id = None
+ self.sa_session.add( event )
+ self.sa_session.flush()
+
+ @property
+ def sa_session( self ):
+ """
+ Returns a SQLAlchemy session -- currently just gets the current
+ session from the threadlocal session context, but this is provided
+ to allow migration toward a more SQLAlchemy 0.4 style of use.
+ """
+ return self.app.model.context.current
+
+ def expunge_all( self ):
+ app = self.app
+ context = app.model.context
+ context.expunge_all()
+ # This is a bit hacky, should refctor this. Maybe refactor to app -> expunge_all()
+ if hasattr(app, 'install_model'):
+ install_model = app.install_model
+ if install_model != app.model:
+ install_model.context.expunge_all()
+
+ def get_toolbox(self):
+ """Returns the application toolbox"""
+ return self.app.toolbox
+
+ @property
+ def model( self ):
+ return self.app.model
+
+ @property
+ def install_model( self ):
+ return self.app.install_model
+
+ def request_types(self):
+ if self.sa_session.query( self.app.model.RequestType ).filter_by( deleted=False ).count() > 0:
+ return True
+ return False
+
+
+class ProvidesUserContext( object ):
+ """ For transaction-like objects to provide Galaxy convience layer for
+ reasoning about users.
+
+ Mixed in class must provide `user`, `api_inherit_admin`, and `app`
+ properties.
+ """
+
+ @property
+ def anonymous( self ):
+ return self.user is None and not self.api_inherit_admin
+
+ def get_current_user_roles( self ):
+ user = self.user
+ if user:
+ roles = user.all_roles()
+ else:
+ roles = []
+ return roles
+
+ def user_is_admin( self ):
+ if self.api_inherit_admin:
+ return True
+ return self.user and self.user.email in self.app.config.admin_users_list
+
+ def user_can_do_run_as( self ):
+ run_as_users = [ user for user in self.app.config.get( "api_allow_run_as", "" ).split( "," ) if user ]
+ if not run_as_users:
+ return False
+ user_in_run_as_users = self.user and self.user.email in run_as_users
+ # Can do if explicitly in list or master_api_key supplied.
+ can_do_run_as = user_in_run_as_users or self.api_inherit_admin
+ return can_do_run_as
+
+ @property
+ def user_ftp_dir( self ):
+ identifier = self.app.config.ftp_upload_dir_identifier
+ return os.path.join( self.app.config.ftp_upload_dir, getattr( self.user, identifier ) )
+
+
+class ProvidesHistoryContext( object ):
+ """ For transaction-like objects to provide Galaxy convience layer for
+ reasoning about histories.
+
+ Mixed in class must provide `user`, `history`, and `app`
+ properties.
+ """
+
+ def db_dataset_for( self, dbkey ):
+ """
+ Returns the db_file dataset associated/needed by `dataset`, or `None`.
+ """
+ # If no history, return None.
+ if self.history is None:
+ return None
+#TODO: when does this happen? is it Bunch or util.bunch.Bunch?
+ if isinstance( self.history, bunch.Bunch ):
+ # The API presents a Bunch for a history. Until the API is
+ # more fully featured for handling this, also return None.
+ return None
+ datasets = self.sa_session.query( self.app.model.HistoryDatasetAssociation ) \
+ .filter_by( deleted=False, history_id=self.history.id, extension="len" )
+ for ds in datasets:
+ if dbkey == ds.dbkey:
+ return ds
+ return None
+
+ @property
+ def db_builds( self ):
+ """
+ Returns the builds defined by galaxy and the builds defined by
+ the user (chromInfo in history).
+ """
+ # FIXME: This method should be removed
+ return self.app.genome_builds.get_genome_build_names( trans=self )
https://bitbucket.org/galaxy/galaxy-central/commits/35a335db606f/
Changeset: 35a335db606f
User: carlfeberhard
Date: 2014-09-03 21:18:55
Summary: Fix bunch import; fix unit test imports
Affected #: 3 files
diff -r efd4b01d8ae64f6a7d25011295a919fea3324f1d -r 35a335db606f3f53e5028ef5463ba78fe6f72000 lib/galaxy/webapps/demo_sequencer/framework/__init__.py
--- a/lib/galaxy/webapps/demo_sequencer/framework/__init__.py
+++ b/lib/galaxy/webapps/demo_sequencer/framework/__init__.py
@@ -32,7 +32,7 @@
from galaxy.web.framework.formbuilder import FormData
import galaxy.web.framework.base
-from galaxy.web.util.bunch import Bunch
+from galaxy.util.bunch import Bunch
from galaxy.exceptions import MessageException
from galaxy.util import asbool
diff -r efd4b01d8ae64f6a7d25011295a919fea3324f1d -r 35a335db606f3f53e5028ef5463ba78fe6f72000 test/unit/test_galaxy_transactions.py
--- a/test/unit/test_galaxy_transactions.py
+++ b/test/unit/test_galaxy_transactions.py
@@ -1,10 +1,10 @@
from galaxy import model
from galaxy.model import mapping
from galaxy.util import bunch
-from galaxy.web import framework
+from galaxy.managers import context
-class TestTransaction( framework.ProvidesAppContext ):
+class TestTransaction( context.ProvidesAppContext ):
def __init__( self ):
self.app = TestApp()
diff -r efd4b01d8ae64f6a7d25011295a919fea3324f1d -r 35a335db606f3f53e5028ef5463ba78fe6f72000 test/unit/test_routes.py
--- a/test/unit/test_routes.py
+++ b/test/unit/test_routes.py
@@ -1,7 +1,7 @@
#import routes
# Unused but adds some stuff to routes.Mapper.
-from galaxy.web.framework import WebApplication
+from galaxy.web.framework.webapp import WebApplication
from galaxy.webapps.galaxy import buildapp as galaxy_buildapp
from galaxy.web import url_for
https://bitbucket.org/galaxy/galaxy-central/commits/19b109c4caa6/
Changeset: 19b109c4caa6
User: carlfeberhard
Date: 2014-09-03 21:19:09
Summary: merge central
Affected #: 1 file
diff -r 35a335db606f3f53e5028ef5463ba78fe6f72000 -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 test/functional/tools/multi_output_configured.xml
--- a/test/functional/tools/multi_output_configured.xml
+++ b/test/functional/tools/multi_output_configured.xml
@@ -10,6 +10,8 @@
echo "3" > subdir2/CUSTOM_3.txt;
mkdir subdir3;
echo "Foo" > subdir3/Foo;
+ echo "mapped reads" > split_bam_.MAPPED.bam;
+ echo "unmapped reads" > split_bam_.UNMAPPED.bam;
</command><inputs><param name="num_param" type="integer" value="7" />
@@ -17,9 +19,10 @@
</inputs><outputs><data format="txt" name="report">
- <discover_datasets pattern="__designation_and_ext__" directory="subdir1" />
- <discover_datasets pattern="CUSTOM_(?P<designation>.+)\.(?P<ext>.+)" directory="subdir2" />
- <discover_datasets pattern="__designation__" directory="subdir3" ext="input" />
+ <discover_datasets pattern="__designation_and_ext__" directory="subdir1" visible="true" />
+ <discover_datasets pattern="CUSTOM_(?P<designation>.+)\.(?P<ext>.+)" directory="subdir2" visible="true" />
+ <discover_datasets pattern="__designation__" directory="subdir3" ext="input" visible="true" />
+ <discover_datasets pattern="split_bam_\.(?P<designation>([A-Z-])\w+)\.bam" ext="txt" visible="true" /></data></outputs><tests>
@@ -45,6 +48,12 @@
<discovered_dataset designation="Foo" ftype="txt"><assert_contents><has_line line="Foo" /></assert_contents></discovered_dataset>
+ <discovered_dataset designation="MAPPED" ftype="txt">
+ <assert_contents><has_line line="mapped reads" /></assert_contents>
+ </discovered_dataset>
+ <discovered_dataset designation="UNMAPPED" ftype="txt">
+ <assert_contents><has_line line="unmapped reads" /></assert_contents>
+ </discovered_dataset></output></test></tests>
https://bitbucket.org/galaxy/galaxy-central/commits/520eb21f9a1e/
Changeset: 520eb21f9a1e
User: carlfeberhard
Date: 2014-09-04 16:09:13
Summary: merge central
Affected #: 16 files
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 .hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -18,4 +18,4 @@
81fbe25bd02edcd53065e8e4476dd1dfb5a72cf2 latest_2013.11.04
2a756ca2cb1826db7796018e77d12e2dd7b67603 latest_2014.02.10
ca45b78adb4152fc6e7395514d46eba6b7d0b838 release_2014.08.11
-ea12550fbc34260ae70bde38db59a4024f35f988 latest_2014.08.11
+20f4fdf1735aeff23a6e7ab00389781fe3f3142c latest_2014.08.11
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -248,6 +248,10 @@
def get_initial_value( self, trans, context, history=None ):
return self.value
+ def to_dict( self, trans, view='collection', value_mapper=None ):
+ d = super(TextToolParameter, self).to_dict(trans)
+ d['area'] = self.area
+ return d
class IntegerToolParameter( TextToolParameter ):
"""
@@ -459,6 +463,13 @@
else:
return self.falsevalue
+ def to_dict( self, trans, view='collection', value_mapper=None ):
+ d = super(BooleanToolParameter, self).to_dict(trans)
+ d['value'] = self.checked
+ d['truevalue'] = self.truevalue
+ d['falsevalue'] = self.falsevalue
+ return d
+
@property
def legal_values( self ):
return [ self.truevalue, self.falsevalue ]
@@ -992,7 +1003,8 @@
value = option[1]
d[ 'value' ] = value
- d[ 'display' ] = self.display
+ d['display'] = self.display
+ d['multiple'] = self.multiple
return d
@@ -1251,7 +1263,10 @@
d = super( ColumnListParameter, self ).to_dict( trans )
# add data reference
- d[ 'data_ref' ] = self.data_ref
+ d['data_ref'] = self.data_ref
+
+ # add numerical flag
+ d['numerical'] = self.numerical
# return
return d
@@ -2008,6 +2023,11 @@
ref = ref()
return ref
+ def to_dict( self, trans, view='collection', value_mapper=None ):
+ d = super( DataToolParameter, self ).to_dict( trans )
+ d['extensions'] = self.extensions
+ d['multiple'] = self.multiple
+ return d
class DataCollectionToolParameter( BaseDataToolParameter ):
"""
@@ -2110,6 +2130,8 @@
elif isinstance( value, basestring ):
if value.startswith( "dce:" ):
rval = trans.sa_session.query( trans.app.model.DatasetCollectionElement ).get( value[ len( "dce:"): ] )
+ elif value.startswith( "hdca:" ):
+ rval = trans.sa_session.query( trans.app.model.HistoryDatasetCollectionAssociation ).get( value[ len( "hdca:"): ] )
else:
rval = trans.sa_session.query( trans.app.model.HistoryDatasetCollectionAssociation ).get( value )
if rval and isinstance( rval, trans.app.model.HistoryDatasetCollectionAssociation ):
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/scripts/mvc/tools/tools-form.js
--- a/static/scripts/mvc/tools/tools-form.js
+++ b/static/scripts/mvc/tools/tools-form.js
@@ -38,9 +38,6 @@
// reset sequential input definition list
this.input_list = {};
- // create data model
- this.data = new Backbone.Model();
-
// initialize datasets
this.datasets = new ToolDatasets({
success: function() {
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/scripts/mvc/tools/tools-section.js
--- a/static/scripts/mvc/tools/tools-section.js
+++ b/static/scripts/mvc/tools/tools-section.js
@@ -11,12 +11,6 @@
// link inputs
this.inputs = options.inputs;
- // link datasets
- this.datasets = app.datasets;
-
- // link data model
- this.data = app.data;
-
// add table class for tr tag
// this assist in transforming the form into a json structure
options.cls_tr = 'section-row';
@@ -38,12 +32,12 @@
// load settings elements into table
for (var i in this.inputs) {
- this._add(this.inputs[i], this.data);
+ this._add(this.inputs[i]);
}
},
// add table row
- _add: function(input, data) {
+ _add: function(input) {
// link this
var self = this;
@@ -61,7 +55,7 @@
switch(type) {
// conditional field
case 'conditional':
- this._addConditional(input_def, data);
+ this._addConditional(input_def);
break;
// repeat block
case 'repeat':
@@ -69,12 +63,12 @@
break;
// default single element row
default:
- this._addRow(type, input_def, data);
+ this._addRow(type, input_def);
}
},
// add conditional block
- _addConditional: function(input_def, data) {
+ _addConditional: function(input_def) {
// add label to input definition root
input_def.label = input_def.test_param.label;
@@ -82,7 +76,7 @@
input_def.value = input_def.test_param.value;
// build options field
- this._addRow('conditional', input_def, data);
+ this._addRow('conditional', input_def);
// add fields
for (var i in input_def.cases) {
@@ -182,7 +176,7 @@
},
// add table row
- _addRow: function(field_type, input_def, data) {
+ _addRow: function(field_type, input_def) {
// get id
var id = input_def.id;
@@ -193,65 +187,59 @@
switch(field_type) {
// text input field
case 'text' :
- field = this._field_text(input_def, data);
+ field = this._field_text(input_def);
break;
// select field
case 'select' :
- field = this._field_select(input_def, data);
+ field = this._field_select(input_def);
break;
- // radiobox field
- case 'radiobutton' :
- field = this._field_radio(input_def, data);
- break;
-
// dataset
case 'data':
- field = this._field_data(input_def, data);
+ field = this._field_data(input_def);
break;
// dataset column
case 'data_column':
- field = this._field_column(input_def, data);
- break;
-
- // text area field
- case 'textarea' :
- field = this._field_textarea(input_def, data);
+ field = this._field_column(input_def);
break;
// conditional select field
case 'conditional':
- field = this._field_conditional(input_def, data);
+ field = this._field_conditional(input_def);
break;
// hidden field
case 'hidden':
- field = this._field_hidden(input_def, data);
+ field = this._field_hidden(input_def);
break;
// integer field
case 'integer':
- field = this._field_integer(input_def, data);
+ field = this._field_slider(input_def);
break;
+ // float field
+ case 'float':
+ field = this._field_slider(input_def);
+ break;
+
// boolean field
case 'boolean':
- field = this._field_radiobutton(input_def, data);
+ field = this._field_boolean(input_def);
break;
// default
default:
- field = this._field_text(input_def, data);
+ field = this._field_text(input_def);
console.debug('tools-form::_addRow() : Unmatched field type (' + field_type + ').');
}
- // set value
- if (!data.get(id)) {
- data.set(id, input_def.value);
+ // set field value
+ if (input_def.value !== undefined) {
+ field.value(input_def.value);
}
- field.value(data.get(id));
// add to field list
this.app.field_list[id] = field;
@@ -272,7 +260,7 @@
},
// conditional input field
- _field_conditional : function(input_def, data) {
+ _field_conditional : function(input_def) {
// link this
var self = this;
@@ -287,15 +275,10 @@
}
// select field
- var id = input_def.id;
return new Ui.Select.View({
- id : 'field-' + id,
+ id : 'field-' + input_def.id,
data : options,
- value : data.get(id),
onchange : function(value) {
- // update value
- data.set(id, value);
-
// check value in order to hide/show options
for (var i in input_def.cases) {
// get case
@@ -329,7 +312,7 @@
},
// data input field
- _field_data : function(input_def, data) {
+ _field_data : function(input_def) {
// link this
var self = this;
@@ -337,7 +320,7 @@
var id = input_def.id;
// get datasets
- var datasets = this.datasets.filterType();
+ var datasets = this.app.datasets.filterType();
// configure options fields
var options = [];
@@ -353,15 +336,19 @@
id : 'field-' + id,
data : options,
value : options[0].value,
+ multiple : input_def.multiple,
onchange : function(value) {
- // update value
- data.set(id, value);
+ // pick the first dataset if multiple might be selected
+ // TODO: iterate over all datasets and filter common/consistent columns
+ if (input_def.multiple) {
+ value = value[0];
+ }
// get referenced columns
- var column_list = self.app.tree.findReferences(id);
+ var column_list = self.app.tree.findReferences(id, 'data_column');
// find selected dataset
- var dataset = self.datasets.filter(value);
+ var dataset = self.app.datasets.filter(value);
// check dataset
if (dataset && column_list.length > 0) {
@@ -376,19 +363,34 @@
console.debug('tool-form::field_data() - FAILED: Could not find metadata for dataset ' + value + '.');
}
- // load column options
- var columns = [];
- for (var key in meta) {
- // add to selection
- columns.push({
- 'label' : 'Column: ' + (parseInt(key) + 1) + ' [' + meta[key] + ']',
- 'value' : key
- });
- }
-
// update referenced columns
for (var i in column_list) {
- var column_field = self.app.field_list[column_list[i]]
+ // get column input/field
+ var column_input = self.app.input_list[column_list[i]];
+ var column_field = self.app.field_list[column_list[i]];
+ if (!column_input || !column_field) {
+ console.debug('tool-form::field_data() - FAILED: Column not found.');
+ }
+
+ // is numerical?
+ var numerical = column_input.numerical;
+
+ // identify column options
+ var columns = [];
+ for (var key in meta) {
+ // get column type
+ var column_type = meta[key];
+
+ // add to selection
+ if (column_type == 'int' || column_type == 'float' || !numerical) {
+ columns.push({
+ 'label' : 'Column: ' + (parseInt(key) + 1) + ' [' + meta[key] + ']',
+ 'value' : key
+ });
+ }
+ }
+
+ // update field
if (column_field) {
column_field.update(columns);
if (!column_field.exists(column_field.value())) {
@@ -404,20 +406,8 @@
});
},
- // column selection field
- _field_column : function (input_def, data) {
- var id = input_def.id;
- return new Ui.Select.View({
- id : 'field-' + id,
- value : data.get(id),
- onchange : function(value) {
- data.set(id, value);
- }
- });
- },
-
// select field
- _field_select : function (input_def, data) {
+ _field_select : function (input_def) {
// configure options fields
var options = [];
for (var i in input_def.options) {
@@ -430,95 +420,66 @@
// identify display type
var SelectClass = Ui.Select;
- if (input_def.display == 'checkboxes') {
+ switch (input_def.display) {
+ case 'checkboxes':
+ SelectClass = Ui.Checkbox;
+ break;
+ case 'radio':
+ SelectClass = Ui.RadioButton;
+ break;
+ }
+
+ // force checkboxes if multiple has been selected
+ if (input_def.multiple) {
SelectClass = Ui.Checkbox;
}
// select field
- var id = input_def.id;
return new SelectClass.View({
- id : 'field-' + id,
- data : options,
- value : data.get(id),
- onchange : function(value) {
- data.set(id, value);
- }
+ id : 'field-' + input_def.id,
+ data : options
});
},
-
+
+ // column selection field
+ _field_column : function (input_def) {
+ return new Ui.Select.View({
+ id : 'field-' + input_def.id,
+ multiple: input_def.multiple
+ });
+ },
+
// text input field
- _field_text : function(input_def, data) {
- var id = input_def.id;
+ _field_text : function(input_def) {
return new Ui.Input({
- id : 'field-' + id,
- value : data.get(id),
- onchange : function(value) {
- data.set(id, value);
- }
+ id : 'field-' + input_def.id,
+ area : input_def.area
});
},
// integer field
- _field_integer: function(input_def, data) {
- var id = input_def.id;
+ _field_slider: function(input_def) {
return new Ui.Slider.View({
- id : 'field-' + id,
- value : data.get(id),
- onchange : function(value) {
- data.set(id, value);
- }
- });
- },
-
- // text area
- _field_textarea : function(input_def, data) {
- var id = input_def.id;
- return new Ui.Textarea({
- id : 'field-' + id,
- onchange : function() {
- data.set(id, field.value());
- }
- });
- },
-
- // radio field
- _field_radio : function(input_def, data) {
- var id = input_def.id;
- return new Ui.RadioButton({
- id : 'field-' + id,
- data : input_def.data,
- value : data.get(id),
- onchange : function(value) {
- data.set(id, value);
- }
+ id : 'field-' + input_def.id,
+ min : input_def.min || 0,
+ max : input_def.max || 1000,
+ decimal : input_def.type == 'float'
});
},
// hidden field
- _field_hidden : function(input_def, data) {
- var id = input_def.id;
+ _field_hidden : function(input_def) {
return new Ui.Hidden({
- id : 'field-' + id,
- value : data.get(id)
+ id : 'field-' + input_def.id
});
},
- // hidden field
- _field_radiobutton : function(input_def, data) {
- var id = input_def.id;
+ // boolean field
+ _field_boolean : function(input_def) {
return new Ui.RadioButton.View({
- id : 'field-' + id,
- value : data.get(id),
- data : [
- {
- label : 'Yes',
- value : 'true'
- },
- {
- label : 'No',
- value : 'false'
- }
- ]
+ id : 'field-' + input_def.id,
+ data : [ { label : 'Yes', value : true },
+ { label : 'No', value : false }]
});
}
});
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/scripts/mvc/tools/tools-tree.js
--- a/static/scripts/mvc/tools/tools-tree.js
+++ b/static/scripts/mvc/tools/tools-tree.js
@@ -66,7 +66,7 @@
},
// find referenced elements
- findReferences: function(identifier) {
+ findReferences: function(identifier, type) {
// referenced elements
var referenced = [];
@@ -105,7 +105,7 @@
}
// check for referenced element
- if (input.data_ref == name) {
+ if (input.data_ref == name && input.type == type) {
list.push(id);
}
}
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/scripts/mvc/ui/ui-misc.js
--- a/static/scripts/mvc/ui/ui-misc.js
+++ b/static/scripts/mvc/ui/ui-misc.js
@@ -312,7 +312,8 @@
placeholder : '',
disabled : false,
visible : true,
- cls : ''
+ cls : '',
+ area : false
},
// initialize
@@ -352,60 +353,11 @@
// element
_template: function(options) {
- return '<input id="' + options.id + '" type="' + options.type + '" value="' + options.value + '" placeholder="' + options.placeholder + '" class="ui-input ' + options.cls + '">';
- }
-});
-
-// plugin
-var Textarea = Backbone.View.extend({
- // options
- optionsDefault: {
- value : '',
- type : 'text',
- placeholder : '',
- disabled : false,
- visible : true,
- cls : ''
- },
-
- // initialize
- initialize : function(options) {
- // configure options
- this.options = Utils.merge(options, this.optionsDefault);
-
- // create new element
- this.setElement(this._template(this.options));
-
- // disable input field
- if (this.options.disabled) {
- this.$el.prop('disabled', true);
+ if (options.area) {
+ return '<textarea id="' + options.id + '" class="ui-textarea ' + options.cls + '"></textarea>';
+ } else {
+ return '<input id="' + options.id + '" type="' + options.type + '" value="' + options.value + '" placeholder="' + options.placeholder + '" class="ui-input ' + options.cls + '">';
}
-
- // hide input field
- if (!this.options.visible) {
- this.$el.hide();
- }
-
- // onchange event handler. fires on user activity.
- var self = this;
- this.$el.on('input', function() {
- if (self.options.onchange) {
- self.options.onchange(self.$el.val());
- }
- });
- },
-
- // value
- value : function (new_val) {
- if (new_val !== undefined) {
- this.$el.val(new_val);
- }
- return this.$el.val();
- },
-
- // element
- _template: function(options) {
- return '<textarea id="' + options.id + '" class="ui-textarea ' + options.cls + '" rows="5"></textarea>';
}
});
@@ -455,7 +407,6 @@
Checkbox : Checkbox,
Searchbox : Searchbox,
Select : Select,
- Textarea : Textarea,
Hidden : Hidden,
Slider : Slider
}
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/scripts/mvc/ui/ui-select-default.js
--- a/static/scripts/mvc/ui/ui-select-default.js
+++ b/static/scripts/mvc/ui/ui-select-default.js
@@ -7,24 +7,19 @@
var View = Backbone.View.extend({
// options
optionsDefault : {
- id : '',
- cls : '',
- empty : 'No data available',
- visible : true,
- wait : false
+ id : '',
+ cls : '',
+ empty : 'No data available',
+ visible : true,
+ wait : false,
+ multiple : false
},
- // value
- selected : null,
-
// initialize
initialize : function(options) {
// configure options
this.options = Utils.merge(options, this.optionsDefault);
- // initial value
- this.selected = this.options.value;
-
// create new element
this.setElement(this._template(this.options));
@@ -32,21 +27,17 @@
this.$select = this.$el.find('#select');
this.$icon = this.$el.find('#icon');
- // add change event. fires only on user activity
- var self = this;
- this.$select.on('change', function() {
- self.value(self.$select.val());
- });
-
- // add change event. fires on trigger
- this.on('change', function() {
- if (self.options.onchange) {
- self.options.onchange(self.value());
- }
- });
+ // configure multiple
+ if (this.options.multiple) {
+ this.$select.prop('multiple', true);
+ this.$select.addClass('ui-select-multiple');
+ this.$icon.remove();
+ } else {
+ this.$el.addClass('ui-select');
+ }
// refresh
- this._refresh();
+ this.update(this.options.data);
// show/hide
if (!this.options.visible) {
@@ -59,31 +50,25 @@
} else {
this.show();
}
+
+ // add change event. fires only on user activity
+ var self = this;
+ this.$select.on('change', function() {
+ self._change();
+ });
+
+ // add change event. fires on trigger
+ this.on('change', function() {
+ self._change();
+ });
},
// value
value : function (new_value) {
-
- // get current id/value
- var before = this.selected;
-
- // check if new_value is defined
if (new_value !== undefined) {
- this.selected = new_value;
this.$select.val(new_value);
}
-
- // get current id/value
- var after = this.selected;
- if (after) {
- // fire onchange
- if (after != before && this.options.onchange) {
- this.options.onchange(after);
- }
- }
-
- // return
- return after;
+ return this.$select.val();
},
// first
@@ -157,6 +142,9 @@
// render
update: function(options) {
+ // backup current value
+ var current = this.$select.val();
+
// remove all options
this.$select.find('option').remove();
@@ -167,6 +155,14 @@
// refresh
this._refresh();
+
+ // set previous value
+ this.$select.val(current);
+
+ // check if any value was set
+ if (!this.$select.val()) {
+ this.$select.val(this.first());
+ }
},
// set on change event
@@ -179,6 +175,13 @@
return this.$select.find('option[value=' + value + ']').length > 0;
},
+ // change
+ _change: function() {
+ if (this.options.onchange) {
+ this.options.onchange(this.$select.val());
+ }
+ },
+
// refresh
_refresh: function() {
// remove placeholder
@@ -196,41 +199,21 @@
// enable select field
this.enable();
}
-
- // update value
- if (this.selected) {
- this.$select.val(this.selected);
- }
},
- // option
+ // template option
_templateOption: function(options) {
return '<option value="' + options.value + '">' + options.label + '</option>';
},
- // element
+ // template
_template: function(options) {
- var tmpl = '<div id="' + options.id + '" class="ui-select">' +
- '<div class="button">' +
- '<i id="icon"/>' +
- '</div>' +
- '<select id="select" class="select ' + options.cls + ' ' + options.id + '">';
- for (key in options.data) {
- // options
- var item = options.data[key];
-
- // identify selected value
- var tag = '';
- if (item.value == options.value || item.value == '') {
- tag = 'selected';
- }
-
- // add template string
- tmpl += '<option value="' + item.value + '" ' + tag + '>' + item.label + '</option>';
- }
- tmpl += '</select>' +
- '</div>';
- return tmpl;
+ return '<div id="' + options.id + '">' +
+ '<div class="button">' +
+ '<i id="icon"/>' +
+ '</div>' +
+ '<select id="select" class="select ' + options.cls + ' ' + options.id + '"></select>' +
+ '</div>';
}
});
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/scripts/mvc/ui/ui-slider.js
--- a/static/scripts/mvc/ui/ui-slider.js
+++ b/static/scripts/mvc/ui/ui-slider.js
@@ -7,7 +7,9 @@
optionsDefault: {
value : '',
min : 1,
- max : 100
+ max : 100,
+ step : 0.1,
+ decimal : false
},
// initialize
@@ -27,6 +29,11 @@
// backup integer field
this.$text = this.$el.find('#text');
+ // set step size
+ if (!this.options.decimal) {
+ this.options.step = 1;
+ }
+
// load slider plugin
this.$slider.slider(this.options);
@@ -38,7 +45,8 @@
// add text field event
this.$text.on('keydown', function (event) {
var v = event.which;
- if (!(v == 13 || v == 8 || v == 189 || (v >= 48 && v <= 57))) {
+ if (!(v == 13 || v == 8 || v == 37 || v == 39 || v == 189 || (v >= 48 && v <= 57)
+ || (self.options.decimal && $(this).val().indexOf('.') == -1) && v == 190)) {
event.preventDefault();
}
});
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/scripts/packed/mvc/tools/tools-form.js
--- a/static/scripts/packed/mvc/tools/tools-form.js
+++ b/static/scripts/packed/mvc/tools/tools-form.js
@@ -1,1 +1,1 @@
-define(["mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/citation/citation-model","mvc/citation/citation-view","mvc/tools","mvc/tools/tools-template","mvc/tools/tools-datasets","mvc/tools/tools-section","mvc/tools/tools-tree"],function(g,k,i,a,f,d,h,j,c){var e=Backbone.Model.extend({initialize:function(l){this.url=galaxy_config.root+"api/tools/"+l.id+"?io_details=true"}});var b=Backbone.View.extend({main_el:"body",initialize:function(m){var l=this;this.options=m;this.model=new e({id:m.id});this.tree=new c(this);this.field_list={};this.input_list={};this.data=new Backbone.Model();this.datasets=new h({success:function(){l._initializeToolForm()}})},_initializeToolForm:function(){var l=this;this.model.fetch({error:function(m){console.debug("tools-form::_initializeToolForm() : Attempt to fetch tool model failed.")},success:function(){l.inputs=l.model.get("inputs");l.portlet=new g.View({icon:"fa-wrench",title:"<b>"+l.model.get("name")+"</b> "+l.model.get("description"),buttons:{execute:new k.ButtonIcon({icon:"fa-check",tooltip:"Execute the tool",title:"Execute",floating:"clear",onclick:function(){console.log(l.tree.create(l))}})}});l.message=new k.Message();l.portlet.append(l.message.$el);$(l.main_el).append(l.portlet.$el);if(l.options.help!=""){$(l.main_el).append(d.help(l.options.help))}if(l.options.citations){$(l.main_el).append(d.citations());var m=new i.ToolCitationCollection();m.tool_id=l.options.id;var n=new a.CitationListView({collection:m});n.render();m.fetch()}l.setElement(l.portlet.content());l.section=new j.View(l,{inputs:l.model.get("inputs")});l.portlet.append(l.section.$el);l.refresh()}})},refresh:function(){this.tree.refresh();for(var l in this.field_list){this.field_list[l].trigger("change")}console.debug("tools-form::refresh() - Recreated tree structure. Refresh.")}});return{View:b}});
\ No newline at end of file
+define(["mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/citation/citation-model","mvc/citation/citation-view","mvc/tools","mvc/tools/tools-template","mvc/tools/tools-datasets","mvc/tools/tools-section","mvc/tools/tools-tree"],function(g,k,i,a,f,d,h,j,c){var e=Backbone.Model.extend({initialize:function(l){this.url=galaxy_config.root+"api/tools/"+l.id+"?io_details=true"}});var b=Backbone.View.extend({main_el:"body",initialize:function(m){var l=this;this.options=m;this.model=new e({id:m.id});this.tree=new c(this);this.field_list={};this.input_list={};this.datasets=new h({success:function(){l._initializeToolForm()}})},_initializeToolForm:function(){var l=this;this.model.fetch({error:function(m){console.debug("tools-form::_initializeToolForm() : Attempt to fetch tool model failed.")},success:function(){l.inputs=l.model.get("inputs");l.portlet=new g.View({icon:"fa-wrench",title:"<b>"+l.model.get("name")+"</b> "+l.model.get("description"),buttons:{execute:new k.ButtonIcon({icon:"fa-check",tooltip:"Execute the tool",title:"Execute",floating:"clear",onclick:function(){console.log(l.tree.create(l))}})}});l.message=new k.Message();l.portlet.append(l.message.$el);$(l.main_el).append(l.portlet.$el);if(l.options.help!=""){$(l.main_el).append(d.help(l.options.help))}if(l.options.citations){$(l.main_el).append(d.citations());var m=new i.ToolCitationCollection();m.tool_id=l.options.id;var n=new a.CitationListView({collection:m});n.render();m.fetch()}l.setElement(l.portlet.content());l.section=new j.View(l,{inputs:l.model.get("inputs")});l.portlet.append(l.section.$el);l.refresh()}})},refresh:function(){this.tree.refresh();for(var l in this.field_list){this.field_list[l].trigger("change")}console.debug("tools-form::refresh() - Recreated tree structure. Refresh.")}});return{View:b}});
\ No newline at end of file
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/scripts/packed/mvc/tools/tools-section.js
--- a/static/scripts/packed/mvc/tools/tools-section.js
+++ b/static/scripts/packed/mvc/tools/tools-section.js
@@ -1,1 +1,1 @@
-define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-tabs"],function(c,b,e,a){var d=Backbone.View.extend({initialize:function(g,f){this.app=g;this.inputs=f.inputs;this.datasets=g.datasets;this.data=g.data;f.cls_tr="section-row";this.table=new b.View(f);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var f in this.inputs){this._add(this.inputs[f],this.data)}},_add:function(h,j){var g=this;var f=jQuery.extend(true,{},h);f.id=c.uuid();this.app.input_list[f.id]=f;var i=f.type;switch(i){case"conditional":this._addConditional(f,j);break;case"repeat":this._addRepeat(f);break;default:this._addRow(i,f,j)}},_addConditional:function(f,j){f.label=f.test_param.label;f.value=f.test_param.value;this._addRow("conditional",f,j);for(var h in f.cases){var g=f.id+"-section-"+h;var k=new d(this.app,{inputs:f.cases[h].inputs,cls:"ui-table-plain"});this.table.add("");this.table.add(k.$el);this.table.append(g)}},_addRepeat:function(f){var g=this;var k=new a.View({title_new:"Add "+f.title,max:f.max,onnew:function(){var i=f.id+"-section-"+c.uuid();var m=new d(g.app,{inputs:f.inputs,cls:"ui-table-plain"});k.add({id:i,title:f.title,$el:m.$el,ondel:function(){k.del(i);k.retitle(f.title);g.app.refresh()}});k.retitle(f.title);k.show(i);g.app.refresh()}});for(var j=0;j<f.min;j++){var h=f.id+"-section-"+c.uuid();var l=new d(g.app,{inputs:f.inputs,cls:"ui-table-plain"});k.add({id:h,title:f.title,$el:l.$el})}k.retitle(f.title);this.table.add("");this.table.add(k.$el);this.table.append(f.id)},_addRow:function(i,f,g){var k=f.id;var h=null;switch(i){case"text":h=this._field_text(f,g);break;case"select":h=this._field_select(f,g);break;case"radiobutton":h=this._field_radio(f,g);break;case"data":h=this._field_data(f,g);break;case"data_column":h=this._field_column(f,g);break;case"textarea":h=this._field_textarea(f,g);break;case"conditional":h=this._field_conditional(f,g);break;case"hidden":h=this._field_hidden(f,g);break;case"integer":h=this._field_integer(f,g);break;case"boolean":h=this._field_radiobutton(f,g);break;default:h=this._field_text(f,g);console.debug("tools-form::_addRow() : Unmatched field type ("+i+").")}if(!g.get(k)){g.set(k,f.value)}h.value(g.get(k));this.app.field_list[k]=h;var j=$("<div/>");j.append(h.$el);if(f.help){j.append('<div class="ui-table-form-info">'+f.help+"</div>")}this.table.add('<span class="ui-table-form-title">'+f.label+"</span>","20%");this.table.add(j);this.table.append(k)},_field_conditional:function(f,l){var g=this;var h=[];for(var j in f.test_param.options){var k=f.test_param.options[j];h.push({label:k[0],value:k[1]})}var m=f.id;return new e.Select.View({id:"field-"+m,data:h,value:l.get(m),onchange:function(u){l.set(m,u);for(var s in f.cases){var o=f.cases[s];var r=f.id+"-section-"+s;var n=g.table.get(r);var q=false;for(var p in o.inputs){var t=o.inputs[p].type;if(t&&t!=="hidden"){q=true;break}}if(o.value==u&&q){n.fadeIn("fast")}else{n.hide()}}}})},_field_data:function(f,l){var g=this;var m=f.id;var k=this.datasets.filterType();var h=[];for(var j in k){h.push({label:k[j].get("name"),value:k[j].get("id")})}return new e.Select.View({id:"field-"+m,data:h,value:h[0].value,onchange:function(s){l.set(m,s);var o=g.app.tree.findReferences(m);var u=g.datasets.filter(s);if(u&&o.length>0){console.debug("tool-form::field_data() - Selected dataset "+s+".");var t=u.get("metadata_column_types");if(!t){console.debug("tool-form::field_data() - FAILED: Could not find metadata for dataset "+s+".")}var r=[];for(var q in t){r.push({label:"Column: "+(parseInt(q)+1)+" ["+t[q]+"]",value:q})}for(var p in o){var n=g.app.field_list[o[p]];if(n){n.update(r);if(!n.exists(n.value())){n.value(n.first())}}}}else{console.debug("tool-form::field_data() - FAILED: Could not find dataset "+s+".")}}})},_field_column:function(f,g){var h=f.id;return new e.Select.View({id:"field-"+h,value:g.get(h),onchange:function(i){g.set(h,i)}})},_field_select:function(f,l){var g=[];for(var h in f.options){var j=f.options[h];g.push({label:j[0],value:j[1]})}var k=e.Select;if(f.display=="checkboxes"){k=e.Checkbox}var m=f.id;return new k.View({id:"field-"+m,data:g,value:l.get(m),onchange:function(i){l.set(m,i)}})},_field_text:function(f,g){var h=f.id;return new e.Input({id:"field-"+h,value:g.get(h),onchange:function(i){g.set(h,i)}})},_field_integer:function(f,g){var h=f.id;return new e.Slider.View({id:"field-"+h,value:g.get(h),onchange:function(i){g.set(h,i)}})},_field_textarea:function(f,g){var h=f.id;return new e.Textarea({id:"field-"+h,onchange:function(){g.set(h,field.value())}})},_field_radio:function(f,g){var h=f.id;return new e.RadioButton({id:"field-"+h,data:f.data,value:g.get(h),onchange:function(i){g.set(h,i)}})},_field_hidden:function(f,g){var h=f.id;return new e.Hidden({id:"field-"+h,value:g.get(h)})},_field_radiobutton:function(f,g){var h=f.id;return new e.RadioButton.View({id:"field-"+h,value:g.get(h),data:[{label:"Yes",value:"true"},{label:"No",value:"false"}]})}});return{View:d}});
\ No newline at end of file
+define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-tabs"],function(c,b,e,a){var d=Backbone.View.extend({initialize:function(g,f){this.app=g;this.inputs=f.inputs;f.cls_tr="section-row";this.table=new b.View(f);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var f in this.inputs){this._add(this.inputs[f])}},_add:function(h){var g=this;var f=jQuery.extend(true,{},h);f.id=c.uuid();this.app.input_list[f.id]=f;var i=f.type;switch(i){case"conditional":this._addConditional(f);break;case"repeat":this._addRepeat(f);break;default:this._addRow(i,f)}},_addConditional:function(f){f.label=f.test_param.label;f.value=f.test_param.value;this._addRow("conditional",f);for(var h in f.cases){var g=f.id+"-section-"+h;var j=new d(this.app,{inputs:f.cases[h].inputs,cls:"ui-table-plain"});this.table.add("");this.table.add(j.$el);this.table.append(g)}},_addRepeat:function(f){var g=this;var k=new a.View({title_new:"Add "+f.title,max:f.max,onnew:function(){var i=f.id+"-section-"+c.uuid();var m=new d(g.app,{inputs:f.inputs,cls:"ui-table-plain"});k.add({id:i,title:f.title,$el:m.$el,ondel:function(){k.del(i);k.retitle(f.title);g.app.refresh()}});k.retitle(f.title);k.show(i);g.app.refresh()}});for(var j=0;j<f.min;j++){var h=f.id+"-section-"+c.uuid();var l=new d(g.app,{inputs:f.inputs,cls:"ui-table-plain"});k.add({id:h,title:f.title,$el:l.$el})}k.retitle(f.title);this.table.add("");this.table.add(k.$el);this.table.append(f.id)},_addRow:function(h,f){var j=f.id;var g=null;switch(h){case"text":g=this._field_text(f);break;case"select":g=this._field_select(f);break;case"data":g=this._field_data(f);break;case"data_column":g=this._field_column(f);break;case"conditional":g=this._field_conditional(f);break;case"hidden":g=this._field_hidden(f);break;case"integer":g=this._field_slider(f);break;case"float":g=this._field_slider(f);break;case"boolean":g=this._field_boolean(f);break;default:g=this._field_text(f);console.debug("tools-form::_addRow() : Unmatched field type ("+h+").")}if(f.value!==undefined){g.value(f.value)}this.app.field_list[j]=g;var i=$("<div/>");i.append(g.$el);if(f.help){i.append('<div class="ui-table-form-info">'+f.help+"</div>")}this.table.add('<span class="ui-table-form-title">'+f.label+"</span>","20%");this.table.add(i);this.table.append(j)},_field_conditional:function(f){var g=this;var h=[];for(var j in f.test_param.options){var k=f.test_param.options[j];h.push({label:k[0],value:k[1]})}return new e.Select.View({id:"field-"+f.id,data:h,onchange:function(s){for(var q in f.cases){var m=f.cases[q];var p=f.id+"-section-"+q;var l=g.table.get(p);var o=false;for(var n in m.inputs){var r=m.inputs[n].type;if(r&&r!=="hidden"){o=true;break}}if(m.value==s&&o){l.fadeIn("fast")}else{l.hide()}}}})},_field_data:function(f){var g=this;var l=f.id;var k=this.app.datasets.filterType();var h=[];for(var j in k){h.push({label:k[j].get("name"),value:k[j].get("id")})}return new e.Select.View({id:"field-"+l,data:h,value:h[0].value,onchange:function(u){var s=g.app.tree.findReferences(l,"data_column");var n=g.app.datasets.filter(u);if(n&&s.length>0){console.debug("tool-form::field_data() - Selected dataset "+u+".");var w=n.get("metadata_column_types");if(!w){console.debug("tool-form::field_data() - FAILED: Could not find metadata for dataset "+u+".")}for(var p in s){var q=g.app.input_list[s[p]];var r=g.app.field_list[s[p]];if(!q||!r){console.debug("tool-form::field_data() - FAILED: Column not found.")}var o=q.numerical;var m=[];for(var v in w){var t=w[v];if(t=="int"||t=="float"||!o){m.push({label:"Column: "+(parseInt(v)+1)+" ["+w[v]+"]",value:v})}}if(r){r.update(m);if(!r.exists(r.value())){r.value(r.first())}}}}else{console.debug("tool-form::field_data() - FAILED: Could not find dataset "+u+".")}}})},_field_select:function(f){var g=[];for(var h in f.options){var j=f.options[h];g.push({label:j[0],value:j[1]})}var k=e.Select;switch(f.display){case"checkboxes":k=e.Checkbox;break;case"radio":k=e.RadioButton;break}if(f.multiple){k=e.Checkbox}return new k.View({id:"field-"+f.id,data:g})},_field_column:function(f){return new e.Select.View({id:"field-"+f.id,multiple:f.multiple})},_field_text:function(f){return new e.Input({id:"field-"+f.id,area:f.area})},_field_slider:function(f){return new e.Slider.View({id:"field-"+f.id,min:f.min||0,max:f.max||1000,decimal:f.type=="float"})},_field_hidden:function(f){return new e.Hidden({id:"field-"+f.id})},_field_boolean:function(f){return new e.RadioButton.View({id:"field-"+f.id,data:[{label:"Yes",value:true},{label:"No",value:false}]})}});return{View:d}});
\ No newline at end of file
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/scripts/packed/mvc/tools/tools-tree.js
--- a/static/scripts/packed/mvc/tools/tools-tree.js
+++ b/static/scripts/packed/mvc/tools/tools-tree.js
@@ -1,1 +1,1 @@
-define([],function(){return Backbone.Model.extend({initialize:function(a){this.app=a},refresh:function(){if(!this.app.section){return{}}this.dict={};this.xml=$("<div/>");this._iterate(this.app.section.$el,this.dict,this.xml)},_iterate:function(d,e,b){var a=this;var c=$(d).children();c.each(function(){var i=this;var h=$(i).attr("id");if($(i).hasClass("section-row")||$(i).hasClass("tab-pane")){e[h]={};var f=a.app.input_list[h];if(f){e[h]={input:f}}var g=$('<div id="'+h+'"/>');b.append(g);a._iterate(i,e[h],g)}else{a._iterate(i,e,b)}})},findReferences:function(c){var f=[];var b=this;function d(g,i){var h=$(i).children();var k=[];var j=false;h.each(function(){var n=this;var m=$(n).attr("id");if(m!==c){var l=b.app.input_list[m];if(l){if(l.name==g){j=true;return false}if(l.data_ref==g){k.push(m)}}}});if(!j){f=f.concat(k);h.each(function(){d(g,this)})}}var e=this.xml.find("#"+c);if(e.length>0){var a=this.app.input_list[c];if(a){d(a.name,e.parent())}}return f}})});
\ No newline at end of file
+define([],function(){return Backbone.Model.extend({initialize:function(a){this.app=a},refresh:function(){if(!this.app.section){return{}}this.dict={};this.xml=$("<div/>");this._iterate(this.app.section.$el,this.dict,this.xml)},_iterate:function(d,e,b){var a=this;var c=$(d).children();c.each(function(){var i=this;var h=$(i).attr("id");if($(i).hasClass("section-row")||$(i).hasClass("tab-pane")){e[h]={};var f=a.app.input_list[h];if(f){e[h]={input:f}}var g=$('<div id="'+h+'"/>');b.append(g);a._iterate(i,e[h],g)}else{a._iterate(i,e,b)}})},findReferences:function(c,e){var g=[];var b=this;function d(h,j){var i=$(j).children();var l=[];var k=false;i.each(function(){var o=this;var n=$(o).attr("id");if(n!==c){var m=b.app.input_list[n];if(m){if(m.name==h){k=true;return false}if(m.data_ref==h&&m.type==e){l.push(n)}}}});if(!k){g=g.concat(l);i.each(function(){d(h,this)})}}var f=this.xml.find("#"+c);if(f.length>0){var a=this.app.input_list[c];if(a){d(a.name,f.parent())}}return g}})});
\ No newline at end of file
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/scripts/packed/mvc/ui/ui-misc.js
--- a/static/scripts/packed/mvc/ui/ui-misc.js
+++ b/static/scripts/packed/mvc/ui/ui-misc.js
@@ -1,1 +1,1 @@
-define(["utils/utils","mvc/ui/ui-select-default","mvc/ui/ui-slider","mvc/ui/ui-checkbox","mvc/ui/ui-radiobutton","mvc/ui/ui-button-menu","mvc/ui/ui-modal"],function(e,h,k,i,b,r,c){var q=Backbone.View.extend({optionsDefault:{url:"",cls:""},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options))},_template:function(s){return'<img class="ui-image '+s.cls+'" src="'+s.url+'"/>'}});var l=Backbone.View.extend({optionsDefault:{title:"",cls:""},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options))},title:function(s){this.$el.html(s)},_template:function(s){return'<label class="ui-label '+s.cls+'">'+s.title+"</label>"},value:function(){return options.title}});var d=Backbone.View.extend({optionsDefault:{floating:"right",icon:"",tooltip:"",placement:"bottom",title:"",cls:""},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).tooltip({title:s.tooltip,placement:"bottom"})},_template:function(s){return'<div><span class="fa '+s.icon+'" class="ui-icon"/> '+s.title+"</div>"}});var g=Backbone.View.extend({optionsDefault:{id:null,title:"",floating:"right",cls:"btn btn-default",icon:""},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).on("click",s.onclick);$(this.el).tooltip({title:s.tooltip,placement:"bottom"})},_template:function(s){var t='<button id="'+s.id+'" type="submit" style="float: '+s.floating+';" type="button" class="ui-button '+s.cls+'">';if(s.icon){t+='<i class="icon fa '+s.icon+'"></i> '}t+=s.title+"</button>";return t}});var o=Backbone.View.extend({optionsDefault:{id:null,title:"",floating:"right",cls:"icon-btn",icon:"",tooltip:"",onclick:null},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).on("click",s.onclick);$(this.el).tooltip({title:s.tooltip,placement:"bottom"})},_template:function(s){var t="";if(s.title){t="width: auto;"}var u='<div id="'+s.id+'" style="float: '+s.floating+"; "+t+'" class="ui-button-icon '+s.cls+'">';if(s.title){u+='<div class="button"><i class="icon fa '+s.icon+'"/> <span class="title">'+s.title+"</span></div>"}else{u+='<i class="icon fa '+s.icon+'"/>'}u+="</div>";return u}});var p=Backbone.View.extend({optionsDefault:{title:"",cls:""},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).on("click",s.onclick)},_template:function(s){return'<div><a href="javascript:void(0)" class="ui-anchor '+s.cls+'">'+s.title+"</a></div>"}});var a=Backbone.View.extend({optionsDefault:{message:"",status:"info",persistent:false},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement("<div></div>")},update:function(t){this.options=e.merge(t,this.optionsDefault);if(t.message!=""){this.$el.html(this._template(this.options));this.$el.find(".alert").append(t.message);this.$el.fadeIn();if(!t.persistent){var s=this;window.setTimeout(function(){if(s.$el.is(":visible")){s.$el.fadeOut()}else{s.$el.hide()}},3000)}}else{this.$el.fadeOut()}},_template:function(s){return'<div class="ui-message alert alert-'+s.status+'"/>'}});var f=Backbone.View.extend({optionsDefault:{onclick:null,searchword:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault);this.setElement(this._template(this.options));var s=this;if(this.options.onclick){this.$el.on("submit",function(v){var u=s.$el.find("#search");s.options.onclick(u.val())})}},_template:function(s){return'<div class="ui-search"><form onsubmit="return false;"><input id="search" class="form-control input-sm" type="text" name="search" placeholder="Search..." value="'+s.searchword+'"><button type="submit" class="btn search-btn"><i class="fa fa-search"></i></button></form></div>'}});var n=Backbone.View.extend({optionsDefault:{value:"",type:"text",placeholder:"",disabled:false,visible:true,cls:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault);this.setElement(this._template(this.options));if(this.options.disabled){this.$el.prop("disabled",true)}if(!this.options.visible){this.$el.hide()}var s=this;this.$el.on("input",function(){if(s.options.onchange){s.options.onchange(s.$el.val())}})},value:function(s){if(s!==undefined){this.$el.val(s)}return this.$el.val()},_template:function(s){return'<input id="'+s.id+'" type="'+s.type+'" value="'+s.value+'" placeholder="'+s.placeholder+'" class="ui-input '+s.cls+'">'}});var j=Backbone.View.extend({optionsDefault:{value:"",type:"text",placeholder:"",disabled:false,visible:true,cls:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault);this.setElement(this._template(this.options));if(this.options.disabled){this.$el.prop("disabled",true)}if(!this.options.visible){this.$el.hide()}var s=this;this.$el.on("input",function(){if(s.options.onchange){s.options.onchange(s.$el.val())}})},value:function(s){if(s!==undefined){this.$el.val(s)}return this.$el.val()},_template:function(s){return'<textarea id="'+s.id+'" class="ui-textarea '+s.cls+'" rows="5"></textarea>'}});var m=Backbone.View.extend({optionsDefault:{value:""},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options))},value:function(s){if(s!==undefined){this.$el.val(s)}return this.$el.val()},_template:function(s){return'<hidden id="'+s.id+'" value="'+s.value+'"/>'}});return{Anchor:p,Button:g,ButtonIcon:o,ButtonMenu:r,Icon:d,Image:q,Input:n,Label:l,Message:a,Modal:c,RadioButton:b,Checkbox:i,Searchbox:f,Select:h,Textarea:j,Hidden:m,Slider:k}});
\ No newline at end of file
+define(["utils/utils","mvc/ui/ui-select-default","mvc/ui/ui-slider","mvc/ui/ui-checkbox","mvc/ui/ui-radiobutton","mvc/ui/ui-button-menu","mvc/ui/ui-modal"],function(l,b,f,e,m,q,n){var d=Backbone.View.extend({optionsDefault:{url:"",cls:""},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options))},_template:function(r){return'<img class="ui-image '+r.cls+'" src="'+r.url+'"/>'}});var k=Backbone.View.extend({optionsDefault:{title:"",cls:""},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options))},title:function(r){this.$el.html(r)},_template:function(r){return'<label class="ui-label '+r.cls+'">'+r.title+"</label>"},value:function(){return options.title}});var c=Backbone.View.extend({optionsDefault:{floating:"right",icon:"",tooltip:"",placement:"bottom",title:"",cls:""},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).tooltip({title:r.tooltip,placement:"bottom"})},_template:function(r){return'<div><span class="fa '+r.icon+'" class="ui-icon"/> '+r.title+"</div>"}});var h=Backbone.View.extend({optionsDefault:{id:null,title:"",floating:"right",cls:"btn btn-default",icon:""},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).on("click",r.onclick);$(this.el).tooltip({title:r.tooltip,placement:"bottom"})},_template:function(r){var s='<button id="'+r.id+'" type="submit" style="float: '+r.floating+';" type="button" class="ui-button '+r.cls+'">';if(r.icon){s+='<i class="icon fa '+r.icon+'"></i> '}s+=r.title+"</button>";return s}});var i=Backbone.View.extend({optionsDefault:{id:null,title:"",floating:"right",cls:"icon-btn",icon:"",tooltip:"",onclick:null},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).on("click",r.onclick);$(this.el).tooltip({title:r.tooltip,placement:"bottom"})},_template:function(r){var s="";if(r.title){s="width: auto;"}var t='<div id="'+r.id+'" style="float: '+r.floating+"; "+s+'" class="ui-button-icon '+r.cls+'">';if(r.title){t+='<div class="button"><i class="icon fa '+r.icon+'"/> <span class="title">'+r.title+"</span></div>"}else{t+='<i class="icon fa '+r.icon+'"/>'}t+="</div>";return t}});var g=Backbone.View.extend({optionsDefault:{title:"",cls:""},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).on("click",r.onclick)},_template:function(r){return'<div><a href="javascript:void(0)" class="ui-anchor '+r.cls+'">'+r.title+"</a></div>"}});var o=Backbone.View.extend({optionsDefault:{message:"",status:"info",persistent:false},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement("<div></div>")},update:function(s){this.options=l.merge(s,this.optionsDefault);if(s.message!=""){this.$el.html(this._template(this.options));this.$el.find(".alert").append(s.message);this.$el.fadeIn();if(!s.persistent){var r=this;window.setTimeout(function(){if(r.$el.is(":visible")){r.$el.fadeOut()}else{r.$el.hide()}},3000)}}else{this.$el.fadeOut()}},_template:function(r){return'<div class="ui-message alert alert-'+r.status+'"/>'}});var a=Backbone.View.extend({optionsDefault:{onclick:null,searchword:""},initialize:function(s){this.options=l.merge(s,this.optionsDefault);this.setElement(this._template(this.options));var r=this;if(this.options.onclick){this.$el.on("submit",function(u){var t=r.$el.find("#search");r.options.onclick(t.val())})}},_template:function(r){return'<div class="ui-search"><form onsubmit="return false;"><input id="search" class="form-control input-sm" type="text" name="search" placeholder="Search..." value="'+r.searchword+'"><button type="submit" class="btn search-btn"><i class="fa fa-search"></i></button></form></div>'}});var j=Backbone.View.extend({optionsDefault:{value:"",type:"text",placeholder:"",disabled:false,visible:true,cls:"",area:false},initialize:function(s){this.options=l.merge(s,this.optionsDefault);this.setElement(this._template(this.options));if(this.options.disabled){this.$el.prop("disabled",true)}if(!this.options.visible){this.$el.hide()}var r=this;this.$el.on("input",function(){if(r.options.onchange){r.options.onchange(r.$el.val())}})},value:function(r){if(r!==undefined){this.$el.val(r)}return this.$el.val()},_template:function(r){if(r.area){return'<textarea id="'+r.id+'" class="ui-textarea '+r.cls+'"></textarea>'}else{return'<input id="'+r.id+'" type="'+r.type+'" value="'+r.value+'" placeholder="'+r.placeholder+'" class="ui-input '+r.cls+'">'}}});var p=Backbone.View.extend({optionsDefault:{value:""},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options))},value:function(r){if(r!==undefined){this.$el.val(r)}return this.$el.val()},_template:function(r){return'<hidden id="'+r.id+'" value="'+r.value+'"/>'}});return{Anchor:g,Button:h,ButtonIcon:i,ButtonMenu:q,Icon:c,Image:d,Input:j,Label:k,Message:o,Modal:n,RadioButton:m,Checkbox:e,Searchbox:a,Select:b,Hidden:p,Slider:f}});
\ No newline at end of file
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/scripts/packed/mvc/ui/ui-select-default.js
--- a/static/scripts/packed/mvc/ui/ui-select-default.js
+++ b/static/scripts/packed/mvc/ui/ui-select-default.js
@@ -1,1 +1,1 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{id:"",cls:"",empty:"No data available",visible:true,wait:false},selected:null,initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.selected=this.options.value;this.setElement(this._template(this.options));this.$select=this.$el.find("#select");this.$icon=this.$el.find("#icon");var c=this;this.$select.on("change",function(){c.value(c.$select.val())});this.on("change",function(){if(c.options.onchange){c.options.onchange(c.value())}});this._refresh();if(!this.options.visible){this.hide()}if(this.options.wait){this.wait()}else{this.show()}},value:function(c){var d=this.selected;if(c!==undefined){this.selected=c;this.$select.val(c)}var e=this.selected;if(e){if(e!=d&&this.options.onchange){this.options.onchange(e)}}return e},first:function(){var c=this.$select.find("option");if(c.length>0){return c.val()}else{return undefined}},text:function(){return this.$select.find("option:selected").text()},show:function(){this.$icon.removeClass();this.$icon.addClass("fa fa-caret-down");this.$select.show();this.$el.show()},hide:function(){this.$el.hide()},wait:function(){this.$icon.removeClass();this.$icon.addClass("fa fa-spinner fa-spin");this.$select.hide()},disabled:function(){return this.$select.is(":disabled")},enable:function(){this.$select.prop("disabled",false)},disable:function(){this.$select.prop("disabled",true)},add:function(c){this.$select.append(this._templateOption(c));this._refresh()},del:function(c){this.$select.find("option[value="+c+"]").remove();this.$select.trigger("change");this._refresh()},update:function(c){this.$select.find("option").remove();for(var d in c){this.$select.append(this._templateOption(c[d]))}this._refresh()},setOnChange:function(c){this.options.onchange=c},exists:function(c){return this.$select.find("option[value="+c+"]").length>0},_refresh:function(){this.$select.find("option[value=null]").remove();var c=this.$select.find("option").length;if(c==0){this.disable();this.$select.append(this._templateOption({value:"null",label:this.options.empty}))}else{this.enable()}if(this.selected){this.$select.val(this.selected)}},_templateOption:function(c){return'<option value="'+c.value+'">'+c.label+"</option>"},_template:function(e){var d='<div id="'+e.id+'" class="ui-select"><div class="button"><i id="icon"/></div><select id="select" class="select '+e.cls+" "+e.id+'">';for(key in e.data){var f=e.data[key];var c="";if(f.value==e.value||f.value==""){c="selected"}d+='<option value="'+f.value+'" '+c+">"+f.label+"</option>"}d+="</select></div>";return d}});return{View:b}});
\ No newline at end of file
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{id:"",cls:"",empty:"No data available",visible:true,wait:false,multiple:false},initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.$select=this.$el.find("#select");this.$icon=this.$el.find("#icon");if(this.options.multiple){this.$select.prop("multiple",true);this.$select.addClass("ui-select-multiple");this.$icon.remove()}else{this.$el.addClass("ui-select")}this.update(this.options.data);if(!this.options.visible){this.hide()}if(this.options.wait){this.wait()}else{this.show()}var c=this;this.$select.on("change",function(){c._change()});this.on("change",function(){c._change()})},value:function(c){if(c!==undefined){this.$select.val(c)}return this.$select.val()},first:function(){var c=this.$select.find("option");if(c.length>0){return c.val()}else{return undefined}},text:function(){return this.$select.find("option:selected").text()},show:function(){this.$icon.removeClass();this.$icon.addClass("fa fa-caret-down");this.$select.show();this.$el.show()},hide:function(){this.$el.hide()},wait:function(){this.$icon.removeClass();this.$icon.addClass("fa fa-spinner fa-spin");this.$select.hide()},disabled:function(){return this.$select.is(":disabled")},enable:function(){this.$select.prop("disabled",false)},disable:function(){this.$select.prop("disabled",true)},add:function(c){this.$select.append(this._templateOption(c));this._refresh()},del:function(c){this.$select.find("option[value="+c+"]").remove();this.$select.trigger("change");this._refresh()},update:function(c){var e=this.$select.val();this.$select.find("option").remove();for(var d in c){this.$select.append(this._templateOption(c[d]))}this._refresh();this.$select.val(e);if(!this.$select.val()){this.$select.val(this.first())}},setOnChange:function(c){this.options.onchange=c},exists:function(c){return this.$select.find("option[value="+c+"]").length>0},_change:function(){if(this.options.onchange){this.options.onchange(this.$select.val())}},_refresh:function(){this.$select.find("option[value=null]").remove();var c=this.$select.find("option").length;if(c==0){this.disable();this.$select.append(this._templateOption({value:"null",label:this.options.empty}))}else{this.enable()}},_templateOption:function(c){return'<option value="'+c.value+'">'+c.label+"</option>"},_template:function(c){return'<div id="'+c.id+'"><div class="button"><i id="icon"/></div><select id="select" class="select '+c.cls+" "+c.id+'"></select></div>'}});return{View:b}});
\ No newline at end of file
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/scripts/packed/mvc/ui/ui-slider.js
--- a/static/scripts/packed/mvc/ui/ui-slider.js
+++ b/static/scripts/packed/mvc/ui/ui-slider.js
@@ -1,1 +1,1 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{value:"",min:1,max:100},initialize:function(d){var c=this;this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.$slider=this.$el.find("#slider");this.$text=this.$el.find("#text");this.$slider.slider(this.options);this.$text.on("change",function(){c.value($(this).val())});this.$text.on("keydown",function(f){var e=f.which;if(!(e==13||e==8||e==189||(e>=48&&e<=57))){f.preventDefault()}});this.$slider.on("slide",function(e,f){c.value(f.value)})},value:function(c){if(c!==undefined){c=Math.max(Math.min(c,this.options.max),this.options.min);if(this.options.onchange){this.options.onchange(c)}this.$slider.slider("value",c);this.$text.val(c)}return this.$text.val()},_template:function(c){return'<div id="'+c.id+'" style=""><input id="text" type="text" class="ui-input" style="width: 50px; float: left"/><div id="slider" style="width: calc(100% - 60px); float: left; top: 8px; left: 10px;"/></div>'}});return{View:b}});
\ No newline at end of file
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{value:"",min:1,max:100,step:0.1,decimal:false},initialize:function(d){var c=this;this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.$slider=this.$el.find("#slider");this.$text=this.$el.find("#text");if(!this.options.decimal){this.options.step=1}this.$slider.slider(this.options);this.$text.on("change",function(){c.value($(this).val())});this.$text.on("keydown",function(f){var e=f.which;if(!(e==13||e==8||e==37||e==39||e==189||(e>=48&&e<=57)||(c.options.decimal&&$(this).val().indexOf(".")==-1)&&e==190)){f.preventDefault()}});this.$slider.on("slide",function(e,f){c.value(f.value)})},value:function(c){if(c!==undefined){c=Math.max(Math.min(c,this.options.max),this.options.min);if(this.options.onchange){this.options.onchange(c)}this.$slider.slider("value",c);this.$text.val(c)}return this.$text.val()},_template:function(c){return'<div id="'+c.id+'" style=""><input id="text" type="text" class="ui-input" style="width: 50px; float: left"/><div id="slider" style="width: calc(100% - 60px); float: left; top: 8px; left: 10px;"/></div>'}});return{View:b}});
\ No newline at end of file
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -148,7 +148,7 @@
.form-control::-moz-placeholder{color:#999}
.form-control:-ms-input-placeholder{color:#999}
.form-control::-webkit-input-placeholder{color:#999}
-.form-control,.ui-input{display:block;width:100%;height:27px;padding:4px 10px;font-size:12px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;border:1px solid #aaa;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6)}
+.form-control,.ui-input,.ui-textarea{display:block;width:100%;height:27px;padding:4px 10px;font-size:12px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;border:1px solid #aaa;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6)}
.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee}
textarea.form-control{height:auto}
.form-group{margin-bottom:15px}
@@ -1282,6 +1282,7 @@
.ui-table tbody{cursor:pointer}
.ui-table-plain tbody td{padding:5px 0px 5px 5px !important;border:none !important}
.ui-table-plain tbody{cursor:auto !important}
+.ui-table-form-info{clear:both !important}
.ui-table-form-separator{font-weight:bold;font-size:0.9em}
.ui-label{font-weight:bold}
.ui-message{padding:2px 2px 2px 10px}
@@ -1289,6 +1290,7 @@
.ui-button-icon{margin-right:5px}.ui-button-icon ul i{font-size:1.2em;margin-right:5px;position:relative;top:1px}
.ui-button-icon .button{margin-right:5px;margin-left:5px}
.ui-button-icon .title{position:relative;font-size:0.8em;font-weight:normal;top:-1px}
+.ui-textarea{height:100px !important}
.ui-tabs .ui-tabs-add{font-size:0.8em;margin-right:5px}
.ui-tabs .ui-tabs-delete{font-size:0.8em;margin-left:5px;cursor:pointer}
.no-highlight{-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-ms-user-select:none;}
@@ -1298,8 +1300,9 @@
.ui-portlet .no-scroll{height:calc(100% - 80px)}
.ui-popover{max-width:700px;display:none}.ui-popover .popover-close{position:absolute;right:10px;top:7px;font-size:1.2em;cursor:pointer}
.ui-popover .popover-title{padding:4px 10px}
-.ui-select{position:relative;height:27px;overflow:hidden;border:1px solid #bfbfbf;-moz-border-radius:3px;border-radius:3px}.ui-select .button{position:relative;width:25px;height:100%;float:right;border-left:1px solid #bfbfbf;padding-left:9px;padding-top:4px;background:#f2f2f2}
+.ui-select,.ui-select-multiple{position:relative;height:27px;overflow:hidden;border:1px solid #bfbfbf;-moz-border-radius:3px;border-radius:3px}.ui-select .button{position:relative;width:25px;height:100%;float:right;border-left:1px solid #bfbfbf;padding-left:9px;padding-top:4px;background:#f2f2f2}
.ui-select select{position:absolute;top:0px;height:100%;width:100%;padding-left:5px;cursor:pointer;background:transparent;border:0;border-radius:0;-webkit-appearance:none}
+.ui-select-multiple{height:100% !important;width:100% !important}
.libraryRow{background-color:#ebd9b2}
.datasetHighlighted{background-color:#f9f9f9}
.libraryItemDeleted-True{font-style:italic}
diff -r 19b109c4caa6b89e5c3e3e7a8f81e4fe1ef21746 -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 static/style/src/less/ui.less
--- a/static/style/src/less/ui.less
+++ b/static/style/src/less/ui.less
@@ -32,6 +32,7 @@
.ui-table-form-info {
&:extend(.toolParamHelp);
+ clear: both !important;
}
.ui-table-form-separator {
@@ -76,6 +77,11 @@
&:extend(.form-control);
}
+.ui-textarea {
+ &:extend(.form-control);
+ height: 100px !important;
+}
+
.ui-tabs {
.ui-tabs-add {
font-size : 0.8em;
@@ -203,4 +209,10 @@
border-radius: 0;
-webkit-appearance: none;
}
+}
+
+.ui-select-multiple {
+ &:extend(.ui-select);
+ height: 100% !important;
+ width: 100% !important;
}
\ No newline at end of file
https://bitbucket.org/galaxy/galaxy-central/commits/b50fbeda0365/
Changeset: b50fbeda0365
User: carlfeberhard
Date: 2014-09-04 16:33:41
Summary: merge central
Affected #: 1 file
diff -r 520eb21f9a1ef33eed6b4451bf6022e1750ff932 -r b50fbeda0365140ea4b91f332f52e56cc4b9d25a lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -2466,6 +2466,7 @@
val = getattr( ldda.datatype, name )
rval['metadata_' + name] = val
return rval
+
def get_template_widgets( self, trans, get_contents=True ):
# See if we have any associated templatesThe get_contents
# param is passed by callers that are inheriting a template - these
@@ -2492,6 +2493,7 @@
else:
return template.get_widgets( trans.user )
return []
+
def templates_dict( self, use_name=False ):
"""
Returns a dict of template info
@@ -2510,6 +2512,7 @@
tmp_dict[ name ] = content.get( field[ 'name' ] )
template_data[template.name] = tmp_dict
return template_data
+
def templates_json( self, use_name=False ):
return json.dumps( self.templates_dict( use_name=use_name ) )
@@ -2533,6 +2536,7 @@
self.info = info
self.inheritable = inheritable
+
class LibraryFolderInfoAssociation( object ):
def __init__( self, folder, form_definition, info, inheritable=False ):
self.folder = folder
@@ -2540,15 +2544,18 @@
self.info = info
self.inheritable = inheritable
+
class LibraryDatasetDatasetInfoAssociation( object ):
def __init__( self, library_dataset_dataset_association, form_definition, info ):
# TODO: need to figure out if this should be inheritable to the associated LibraryDataset
self.library_dataset_dataset_association = library_dataset_dataset_association
self.template = form_definition
self.info = info
+
@property
def inheritable( self ):
- return True #always allow inheriting, used for replacement
+ return True # always allow inheriting, used for replacement
+
class ValidationError( object ):
def __init__( self, message=None, err_type=None, attributes=None ):
@@ -2556,42 +2563,47 @@
self.err_type = err_type
self.attributes = attributes
+
class DatasetToValidationErrorAssociation( object ):
def __init__( self, dataset, validation_error ):
self.dataset = dataset
self.validation_error = validation_error
+
class ImplicitlyConvertedDatasetAssociation( object ):
- def __init__( self, id = None, parent = None, dataset = None, file_type = None, deleted = False, purged = False, metadata_safe = True ):
+
+ def __init__( self, id=None, parent=None, dataset=None, file_type=None, deleted=False, purged=False, metadata_safe=True ):
self.id = id
if isinstance(dataset, HistoryDatasetAssociation):
self.dataset = dataset
elif isinstance(dataset, LibraryDatasetDatasetAssociation):
self.dataset_ldda = dataset
else:
- raise AttributeError, 'Unknown dataset type provided for dataset: %s' % type( dataset )
+ raise AttributeError( 'Unknown dataset type provided for dataset: %s' % type( dataset ) )
if isinstance(parent, HistoryDatasetAssociation):
self.parent_hda = parent
elif isinstance(parent, LibraryDatasetDatasetAssociation):
self.parent_ldda = parent
else:
- raise AttributeError, 'Unknown dataset type provided for parent: %s' % type( parent )
+ raise AttributeError( 'Unknown dataset type provided for parent: %s' % type( parent ) )
self.type = file_type
self.deleted = deleted
self.purged = purged
self.metadata_safe = metadata_safe
- def clear( self, purge = False, delete_dataset = True ):
+ def clear( self, purge=False, delete_dataset=True ):
self.deleted = True
if self.dataset:
if delete_dataset:
self.dataset.deleted = True
if purge:
self.dataset.purged = True
- if purge and self.dataset.deleted: #do something with purging
+ if purge and self.dataset.deleted: # do something with purging
self.purged = True
- try: os.unlink( self.file_name )
- except Exception, e: print "Failed to purge associated file (%s) from disk: %s" % ( self.file_name, e )
+ try:
+ os.unlink( self.file_name )
+ except Exception, e:
+ print "Failed to purge associated file (%s) from disk: %s" % ( self.file_name, e )
DEFAULT_COLLECTION_NAME = "Unnamed Collection"
@@ -2903,6 +2915,7 @@
self.tool_id = None
self.message = message
+
class GalaxySession( object ):
def __init__( self,
id=None,
@@ -2924,24 +2937,29 @@
self.is_valid = is_valid
self.prev_session_id = prev_session_id
self.histories = []
+
def add_history( self, history, association=None ):
if association is None:
self.histories.append( GalaxySessionToHistoryAssociation( self, history ) )
else:
self.histories.append( association )
+
def get_disk_usage( self ):
if self.disk_usage is None:
return 0
return self.disk_usage
+
def set_disk_usage( self, bytes ):
self.disk_usage = bytes
total_disk_usage = property( get_disk_usage, set_disk_usage )
+
class GalaxySessionToHistoryAssociation( object ):
def __init__( self, galaxy_session, history ):
self.galaxy_session = galaxy_session
self.history = history
+
class UCI( object ):
def __init__( self ):
self.id = None
@@ -2962,14 +2980,14 @@
self.latest_workflow_id = None
self.workflows = []
- def copy_tags_from(self,target_user,source_workflow):
+ def copy_tags_from(self, target_user, source_workflow):
for src_swta in source_workflow.owner_tags:
new_swta = src_swta.copy()
new_swta.user = target_user
self.tags.append(new_swta)
- def to_dict( self, view='collection', value_mapper = None ):
- rval = super( StoredWorkflow, self ).to_dict( view=view, value_mapper = value_mapper )
+ def to_dict( self, view='collection', value_mapper=None ):
+ rval = super( StoredWorkflow, self ).to_dict( view=view, value_mapper=value_mapper )
tags_str_list = []
for tag in self.tags:
tag_str = tag.user_tname
@@ -3007,12 +3025,11 @@
return False
def to_dict( self, view='collection', value_mapper=None):
- rval = super( Workflow, self ).to_dict( view=view, value_mapper = value_mapper )
- rval['uuid'] = ( lambda uuid: str( uuid ) if uuid else None )( self.uuid )
+ rval = super( Workflow, self ).to_dict( view=view, value_mapper=value_mapper )
+ rval['uuid'] = ( lambda uuid: str( uuid ) if uuid else None )( self.uuid )
return rval
-
class WorkflowStep( object ):
def __init__( self ):
@@ -3061,7 +3078,7 @@
dict_collection_visible_keys = ( 'id', 'update_time', 'workflow_id' )
dict_element_visible_keys = ( 'id', 'update_time', 'workflow_id' )
- def to_dict( self, view='collection', value_mapper = None ):
+ def to_dict( self, view='collection', value_mapper=None ):
rval = super( WorkflowInvocation, self ).to_dict( view=view, value_mapper=value_mapper )
if view == 'element':
steps = {}
@@ -3072,12 +3089,12 @@
inputs = {}
for step in self.steps:
- if step.workflow_step.type =='tool':
+ if step.workflow_step.type == 'tool':
for step_input in step.workflow_step.input_connections:
if step_input.output_step.type == 'data_input':
for job_input in step.job.input_datasets:
if job_input.name == step_input.input_name:
- inputs[str(step_input.output_step.order_index)] = { "id" : job_input.dataset_id, "src" : "hda"}
+ inputs[str(step_input.output_step.order_index)] = { "id": job_input.dataset_id, "src": "hda"}
rval['inputs'] = inputs
return rval
@@ -3086,19 +3103,20 @@
dict_collection_visible_keys = ( 'id', 'update_time', 'job_id', 'workflow_step_id' )
dict_element_visible_keys = ( 'id', 'update_time', 'job_id', 'workflow_step_id' )
- def to_dict( self, view='collection', value_mapper = None ):
+ def to_dict( self, view='collection', value_mapper=None ):
rval = super( WorkflowInvocationStep, self ).to_dict( view=view, value_mapper=value_mapper )
rval['order_index'] = self.workflow_step.order_index
return rval
class MetadataFile( object ):
- def __init__( self, dataset = None, name = None ):
+ def __init__( self, dataset=None, name=None ):
if isinstance( dataset, HistoryDatasetAssociation ):
self.history_dataset = dataset
elif isinstance( dataset, LibraryDatasetDatasetAssociation ):
self.library_dataset = dataset
self.name = name
+
@property
def file_name( self ):
assert self.id is not None, "ID must be set before filename used (commit the object)"
@@ -3129,14 +3147,15 @@
class FormDefinition( object, Dictifiable ):
# The following form_builder classes are supported by the FormDefinition class.
supported_field_types = [ AddressField, CheckboxField, PasswordField, SelectField, TextArea, TextField, WorkflowField, WorkflowMappingField, HistoryField ]
- types = Bunch( REQUEST = 'Sequencing Request Form',
- SAMPLE = 'Sequencing Sample Form',
- EXTERNAL_SERVICE = 'External Service Information Form',
- RUN_DETAILS_TEMPLATE = 'Sample run details template',
- LIBRARY_INFO_TEMPLATE = 'Library information template',
- USER_INFO = 'User Information' )
+ types = Bunch( REQUEST='Sequencing Request Form',
+ SAMPLE='Sequencing Sample Form',
+ EXTERNAL_SERVICE='External Service Information Form',
+ RUN_DETAILS_TEMPLATE='Sample run details template',
+ LIBRARY_INFO_TEMPLATE='Library information template',
+ USER_INFO='User Information' )
dict_collection_visible_keys = ( 'id', 'name' )
dict_element_visible_keys = ( 'id', 'name', 'desc', 'form_definition_current_id', 'fields', 'layout' )
+
def __init__( self, name=None, desc=None, fields=[], form_definition_current=None, form_type=None, layout=None ):
self.name = name
self.desc = desc
@@ -3144,6 +3163,7 @@
self.form_definition_current = form_definition_current
self.type = form_type
self.layout = layout
+
def grid_fields( self, grid_index ):
# Returns a dictionary whose keys are integers corresponding to field positions
# on the grid and whose values are the field.
@@ -3152,6 +3172,7 @@
if str( f[ 'layout' ] ) == str( grid_index ):
gridfields[i] = f
return gridfields
+
def get_widgets( self, user, contents={}, **kwd ):
'''
Return the list of widgets that comprise a form definition,
@@ -3213,24 +3234,28 @@
field_widget.params = params
elif field_type == 'SelectField':
for option in field[ 'selectlist' ]:
+
if option == value:
field_widget.add_option( option, option, selected=True )
else:
field_widget.add_option( option, option )
elif field_type == 'CheckboxField':
+
field_widget.set_checked( value )
if field[ 'required' ] == 'required':
req = 'Required'
else:
req = 'Optional'
if field[ 'helptext' ]:
- helptext='%s (%s)' % ( field[ 'helptext' ], req )
+ helptext = '%s (%s)' % ( field[ 'helptext' ], req )
else:
helptext = '(%s)' % req
widgets.append( dict( label=field[ 'label' ],
+
widget=field_widget,
helptext=helptext ) )
return widgets
+
def field_as_html( self, field ):
"""Generates disabled html for a field"""
type = field[ 'type' ]
@@ -3245,21 +3270,25 @@
# Return None if unsupported field type
return None
+
class FormDefinitionCurrent( object ):
def __init__(self, form_definition=None):
self.latest_form = form_definition
+
class FormValues( object ):
def __init__(self, form_def=None, content=None):
self.form_definition = form_def
self.content = content
+
class Request( object, Dictifiable ):
- states = Bunch( NEW = 'New',
- SUBMITTED = 'In Progress',
- REJECTED = 'Rejected',
- COMPLETE = 'Complete' )
+ states = Bunch( NEW='New',
+ SUBMITTED='In Progress',
+ REJECTED='Rejected',
+ COMPLETE='Complete' )
dict_collection_visible_keys = ( 'id', 'name', 'state' )
+
def __init__( self, name=None, desc=None, request_type=None, user=None, form_values=None, notification=None ):
self.name = name
self.desc = desc
@@ -3268,17 +3297,20 @@
self.user = user
self.notification = notification
self.samples_list = []
+
@property
def state( self ):
latest_event = self.latest_event
if latest_event:
return latest_event.state
return None
+
@property
def latest_event( self ):
if self.events:
return self.events[0]
return None
+
@property
def samples_have_common_state( self ):
"""
@@ -3294,6 +3326,7 @@
if s.state.id != state_for_comparison.id:
return False
return state_for_comparison
+
@property
def last_comment( self ):
latest_event = self.latest_event
@@ -3302,26 +3335,34 @@
return latest_event.comment
return ''
return 'No comment'
+
def get_sample( self, sample_name ):
for sample in self.samples:
if sample.name == sample_name:
return sample
return None
+
@property
def is_unsubmitted( self ):
return self.state in [ self.states.REJECTED, self.states.NEW ]
+
@property
def is_rejected( self ):
return self.state == self.states.REJECTED
+
@property
def is_submitted( self ):
return self.state == self.states.SUBMITTED
+
@property
def is_new( self ):
+
return self.state == self.states.NEW
+
@property
def is_complete( self ):
return self.state == self.states.COMPLETE
+
@property
def samples_without_library_destinations( self ):
# Return all samples that are not associated with a library
@@ -3330,6 +3371,7 @@
if not sample.library:
samples.append( sample )
return samples
+
@property
def samples_with_bar_code( self ):
# Return all samples that have associated bar code
@@ -3338,6 +3380,7 @@
if sample.bar_code:
samples.append( sample )
return samples
+
def send_email_notification( self, trans, common_state, final_state=False ):
# Check if an email notification is configured to be sent when the samples
# are in this state
@@ -3390,7 +3433,7 @@
try:
send_mail( frm, to, subject, body, trans.app.config )
comments = "Email notification sent to %s." % ", ".join( to ).strip().strip( ',' )
- except Exception,e:
+ except Exception, e:
comments = "Email notification failed. (%s)" % str(e)
# update the request history with the email notification event
elif not trans.app.config.smtp_server:
@@ -3401,16 +3444,19 @@
trans.sa_session.flush()
return comments
+
class RequestEvent( object ):
def __init__(self, request=None, request_state=None, comment=''):
self.request = request
self.state = request_state
self.comment = comment
+
class ExternalService( object ):
- data_transfer_protocol = Bunch( HTTP = 'http',
- HTTPS = 'https',
- SCP = 'scp' )
+ data_transfer_protocol = Bunch( HTTP='http',
+ HTTPS='https',
+ SCP='scp' )
+
def __init__( self, name=None, description=None, external_service_type_id=None, version=None, form_definition_id=None, form_values_id=None, deleted=None ):
self.name = name
self.description = description
@@ -3419,9 +3465,11 @@
self.form_definition_id = form_definition_id
self.form_values_id = form_values_id
self.deleted = deleted
- self.label = None # Used in the request_type controller's __build_external_service_select_field() method
+ self.label = None # Used in the request_type controller's __build_external_service_select_field() method
+
def get_external_service_type( self, trans ):
return trans.app.external_service_types.all_external_service_types[ self.external_service_type_id ]
+
def load_data_transfer_settings( self, trans ):
trans.app.external_service_types.reload( self.external_service_type_id )
self.data_transfer = {}
@@ -3442,33 +3490,39 @@
automatic_transfer = data_transfer_obj.config.get( 'automatic_transfer', 'false' )
http_configs[ 'automatic_transfer' ] = galaxy.util.string_as_bool( automatic_transfer )
self.data_transfer[ self.data_transfer_protocol.HTTP ] = http_configs
+
def populate_actions( self, trans, item, param_dict=None ):
return self.get_external_service_type( trans ).actions.populate( self, item, param_dict=param_dict )
+
class RequestType( object, Dictifiable ):
dict_collection_visible_keys = ( 'id', 'name', 'desc' )
dict_element_visible_keys = ( 'id', 'name', 'desc', 'request_form_id', 'sample_form_id' )
- rename_dataset_options = Bunch( NO = 'Do not rename',
- SAMPLE_NAME = 'Preprend sample name',
- EXPERIMENT_NAME = 'Prepend experiment name',
- EXPERIMENT_AND_SAMPLE_NAME = 'Prepend experiment and sample name')
+ rename_dataset_options = Bunch( NO='Do not rename',
+ SAMPLE_NAME='Preprend sample name',
+ EXPERIMENT_NAME='Prepend experiment name',
+ EXPERIMENT_AND_SAMPLE_NAME='Prepend experiment and sample name')
permitted_actions = get_permitted_actions( filter='REQUEST_TYPE' )
+
def __init__( self, name=None, desc=None, request_form=None, sample_form=None ):
self.name = name
self.desc = desc
self.request_form = request_form
self.sample_form = sample_form
+
@property
def external_services( self ):
external_services = []
for rtesa in self.external_service_associations:
external_services.append( rtesa.external_service )
return external_services
+
def get_external_service( self, external_service_type_id ):
for rtesa in self.external_service_associations:
if rtesa.external_service.external_service_type_id == external_service_type_id:
return rtesa.external_service
return None
+
def get_external_services_for_manual_data_transfer( self, trans ):
'''Returns all external services that use manual data transfer'''
external_services = []
@@ -3481,6 +3535,7 @@
if not transfer_type_settings[ 'automatic_transfer' ]:
external_services.append( external_service )
return external_services
+
def delete_external_service_associations( self, trans ):
'''Deletes all external service associations.'''
flush_needed = False
@@ -3489,20 +3544,24 @@
flush_needed = True
if flush_needed:
trans.sa_session.flush()
+
def add_external_service_association( self, trans, external_service ):
rtesa = trans.model.RequestTypeExternalServiceAssociation( self, external_service )
trans.sa_session.add( rtesa )
trans.sa_session.flush()
+
@property
def final_sample_state( self ):
# The states mapper for this object orders ascending
return self.states[-1]
+
@property
def run_details( self ):
if self.run:
# self.run[0] is [RequestTypeRunAssociation]
return self.run[0]
return None
+
def get_template_widgets( self, trans, get_contents=True ):
# See if we have any associated templates. The get_contents param
# is passed by callers that are inheriting a template - these are
@@ -3520,23 +3579,27 @@
return template.get_widgets( trans.user )
return []
+
class RequestTypeExternalServiceAssociation( object ):
def __init__( self, request_type, external_service ):
self.request_type = request_type
self.external_service = external_service
+
class RequestTypePermissions( object ):
def __init__( self, action, request_type, role ):
self.action = action
self.request_type = request_type
self.role = role
+
class Sample( object, Dictifiable ):
# The following form_builder classes are supported by the Sample class.
supported_field_types = [ CheckboxField, SelectField, TextField, WorkflowField, WorkflowMappingField, HistoryField ]
- bulk_operations = Bunch( CHANGE_STATE = 'Change state',
- SELECT_LIBRARY = 'Select data library and folder' )
+ bulk_operations = Bunch( CHANGE_STATE='Change state',
+ SELECT_LIBRARY='Select data library and folder' )
dict_collection_visible_keys = ( 'id', 'name' )
+
def __init__(self, name=None, desc=None, request=None, form_values=None, bar_code=None, library=None, folder=None, workflow=None, history=None):
self.name = name
self.desc = desc
@@ -3547,17 +3610,20 @@
self.folder = folder
self.history = history
self.workflow = workflow
+
@property
def state( self ):
latest_event = self.latest_event
if latest_event:
return latest_event.state
return None
+
@property
def latest_event( self ):
if self.events:
return self.events[0]
return None
+
@property
def adding_to_library_dataset_files( self ):
adding_to_library_datasets = []
@@ -3565,6 +3631,7 @@
if dataset.status == SampleDataset.transfer_status.ADD_TO_LIBRARY:
adding_to_library_datasets.append( dataset )
return adding_to_library_datasets
+
@property
def inprogress_dataset_files( self ):
inprogress_datasets = []
@@ -3572,6 +3639,7 @@
if dataset.status not in [ SampleDataset.transfer_status.NOT_STARTED, SampleDataset.transfer_status.COMPLETE ]:
inprogress_datasets.append( dataset )
return inprogress_datasets
+
@property
def queued_dataset_files( self ):
queued_datasets = []
@@ -3579,6 +3647,7 @@
if dataset.status == SampleDataset.transfer_status.IN_QUEUE:
queued_datasets.append( dataset )
return queued_datasets
+
@property
def transfer_error_dataset_files( self ):
transfer_error_datasets = []
@@ -3586,6 +3655,7 @@
if dataset.status == SampleDataset.transfer_status.ERROR:
transfer_error_datasets.append( dataset )
return transfer_error_datasets
+
@property
def transferred_dataset_files( self ):
transferred_datasets = []
@@ -3593,6 +3663,7 @@
if dataset.status == SampleDataset.transfer_status.COMPLETE:
transferred_datasets.append( dataset )
return transferred_datasets
+
@property
def transferring_dataset_files( self ):
transferring_datasets = []
@@ -3600,6 +3671,7 @@
if dataset.status == SampleDataset.transfer_status.TRANSFERRING:
transferring_datasets.append( dataset )
return transferring_datasets
+
@property
def untransferred_dataset_files( self ):
untransferred_datasets = []
@@ -3607,6 +3679,7 @@
if dataset.status != SampleDataset.transfer_status.COMPLETE:
untransferred_datasets.append( dataset )
return untransferred_datasets
+
def get_untransferred_dataset_size( self, filepath, scp_configs ):
def print_ticks( d ):
pass
https://bitbucket.org/galaxy/galaxy-central/commits/cebadec812f5/
Changeset: cebadec812f5
User: carlfeberhard
Date: 2014-09-04 17:06:08
Summary: merge central
Affected #: 2 files
diff -r b50fbeda0365140ea4b91f332f52e56cc4b9d25a -r cebadec812f5b1ca10abdebdb60456796e061c56 lib/galaxy/util/__init__.py
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -98,6 +98,21 @@
return False
+def is_uuid( value ):
+ """
+ This method returns True if value is a UUID, otherwise False.
+ >>> is_uuid( "123e4567-e89b-12d3-a456-426655440000" )
+ True
+ >>> is_uuid( "0x3242340298902834" )
+ False
+ """
+ uuid_re = re.compile( "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" )
+ if re.match( uuid_re, str( value ) ):
+ return True
+ else:
+ return False
+
+
def get_charset_from_http_headers( headers, default=None ):
rval = headers.get('content-type', None )
if rval and 'charset=' in rval:
diff -r b50fbeda0365140ea4b91f332f52e56cc4b9d25a -r cebadec812f5b1ca10abdebdb60456796e061c56 lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -433,24 +433,19 @@
return stored_workflow
def __get_stored_workflow( self, trans, workflow_id ):
- try:
+ if util.is_uuid(workflow_id):
+ # see if they have passed in the UUID for a workflow that is attached to a stored workflow
+ workflow_uuid = uuid.UUID(workflow_id)
+ stored_workflow = trans.sa_session.query(trans.app.model.StoredWorkflow).filter( and_(
+ trans.app.model.StoredWorkflow.latest_workflow_id == trans.app.model.Workflow.id,
+ trans.app.model.Workflow.uuid == workflow_uuid
+ )).first()
+ if stored_workflow is None:
+ raise exceptions.ObjectNotFound( "Workflow not found: %s" % workflow_id )
+ else:
workflow_id = self.__decode_id( trans, workflow_id )
query = trans.sa_session.query( trans.app.model.StoredWorkflow )
stored_workflow = query.get( workflow_id )
- except Exception:
- try:
- #see if they have passed in the UUID for a workflow that is attached to a stored workflow
- workflow_uuid = uuid.UUID(workflow_id)
- stored_workflow = trans.sa_session.query(trans.app.model.StoredWorkflow).filter( and_(
- trans.app.model.StoredWorkflow.latest_workflow_id == trans.app.model.Workflow.id,
- trans.app.model.Workflow.uuid == workflow_uuid
- )).first()
- if stored_workflow is None:
- raise exceptions.ObjectNotFound( "Workflow not found: %s" % workflow_id )
- return stored_workflow
- except:
- pass #let the outer raise exception happen
- raise exceptions.ObjectNotFound( "No such workflow found - invalid workflow identifier." )
if stored_workflow is None:
raise exceptions.ObjectNotFound( "No such workflow found." )
return stored_workflow
https://bitbucket.org/galaxy/galaxy-central/commits/404c21fa0de7/
Changeset: 404c21fa0de7
User: carlfeberhard
Date: 2014-09-04 17:06:39
Summary: Fix doc
Affected #: 1 file
diff -r cebadec812f5b1ca10abdebdb60456796e061c56 -r 404c21fa0de76e20deba7b63eebc15cca617173c lib/galaxy/web/framework/webapp.py
--- a/lib/galaxy/web/framework/webapp.py
+++ b/lib/galaxy/web/framework/webapp.py
@@ -55,6 +55,8 @@
class WebApplication( base.WebApplication ):
"""
+ Base WSGI application instantiated for all Galaxy webapps.
+
A web application that:
* adds API and UI controllers by scanning given directories and
importing all modules found there.
https://bitbucket.org/galaxy/galaxy-central/commits/e6546f41d7b2/
Changeset: e6546f41d7b2
User: carlfeberhard
Date: 2014-09-04 22:28:53
Summary: merge central
Affected #: 28 files
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 datatypes_conf.xml.sample
--- a/datatypes_conf.xml.sample
+++ b/datatypes_conf.xml.sample
@@ -312,4 +312,22 @@
<sniffer type="galaxy.datatypes.assembly:Amos"/>
--></sniffers>
+ <build_sites>
+ <!--
+ Build sites define the builds (dbkeys) available at sites used by display
+ applications and the URL to those sites.
+
+ The `display` attributes on the `ucsc` and `gbrowse` sites replace the
+ `ucsc_display_sites` and `gbrowse_display_sites` options in
+ universe_wsgi.ini. Because these are used by "old-style" display
+ applications, their types cannot change if you want the old-style display
+ links for these sites to work.
+ -->
+ <site type="ucsc" file="tool-data/shared/ucsc/ucsc_build_sites.txt" display="main,test,archaea,ucla"/>
+ <site type="gbrowse" file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" display="modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225"/>
+ <site type="ensembl" file="tool-data/shared/ensembl/ensembl_sites.txt"/>
+ <site type="ensembl_data_url" file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt"/>
+ <site type="igv" file="tool-data/shared/igv/igv_build_sites.txt"/>
+ <site type="rviewer" file="tool-data/shared/rviewer/rviewer_build_sites.txt"/>
+ </build_sites></datatypes>
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/ensembl/ensembl_bam.xml
--- a/display_applications/ensembl/ensembl_bam.xml
+++ b/display_applications/ensembl/ensembl_bam.xml
@@ -1,7 +1,7 @@
<display id="ensembl_bam" version="1.0.0" name="display at Ensembl"><!-- Current Ensembl method of attaching user data via URL; archives older than ~November 2008 will use a different method --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/ensembl/ensembl_gff.xml
--- a/display_applications/ensembl/ensembl_gff.xml
+++ b/display_applications/ensembl/ensembl_gff.xml
@@ -1,7 +1,7 @@
<display id="ensembl_gff" version="1.0.0" name="display at Ensembl"><!-- Current Ensembl method of attaching user data via URL; archives older than ~November 2008 will use a different method --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -41,7 +41,7 @@
<!-- Old Ensembl method of attaching user data via URL --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl_data_url" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/ensembl/ensembl_interval_as_bed.xml
--- a/display_applications/ensembl/ensembl_interval_as_bed.xml
+++ b/display_applications/ensembl/ensembl_interval_as_bed.xml
@@ -1,7 +1,7 @@
<display id="ensembl_interval" version="1.0.0" name="display at Ensembl"><!-- Current Ensembl method of attaching user data via URL; archives older than ~November 2008 will use a different method --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -41,7 +41,7 @@
<!-- Old Ensembl method of attaching user data via URL --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl_data_url" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/gbrowse/gbrowse_gff.xml
--- a/display_applications/gbrowse/gbrowse_gff.xml
+++ b/display_applications/gbrowse/gbrowse_gff.xml
@@ -1,6 +1,6 @@
<display id="gbrowse_gff" version="1.0.0" name="display at GBrowse"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="gbrowse" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -10,7 +10,7 @@
<dynamic_param name="site_organisms" value="4" split="True" separator="," /><!-- Filter out some of the links based upon matching site_id to a Galaxy application configuration parameter and by dataset dbkey -->
- <filter>${site_id in $APP.config.gbrowse_display_sites}</filter>
+ <filter>${site_id in $APP.datatypes_registry.get_display_sites('gbrowse')}</filter><filter>${dataset.dbkey in $site_dbkeys}</filter><!-- We define url and params as normal, but values defined in dynamic_param are available by specified name -->
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/gbrowse/gbrowse_interval_as_bed.xml
--- a/display_applications/gbrowse/gbrowse_interval_as_bed.xml
+++ b/display_applications/gbrowse/gbrowse_interval_as_bed.xml
@@ -1,6 +1,6 @@
<display id="gbrowse_interval_as_bed" version="1.0.0" name="display at GBrowse"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="gbrowse" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -10,7 +10,7 @@
<dynamic_param name="site_organisms" value="4" split="True" separator="," /><!-- Filter out some of the links based upon matching site_id to a Galaxy application configuration parameter and by dataset dbkey -->
- <filter>${site_id in $APP.config.gbrowse_display_sites}</filter>
+ <filter>${site_id in $APP.datatypes_registry.get_display_sites('gbrowse')}</filter><filter>${dataset.dbkey in $site_dbkeys}</filter><!-- We define url and params as normal, but values defined in dynamic_param are available by specified name -->
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/gbrowse/gbrowse_wig.xml
--- a/display_applications/gbrowse/gbrowse_wig.xml
+++ b/display_applications/gbrowse/gbrowse_wig.xml
@@ -1,6 +1,6 @@
<display id="gbrowse_wig" version="1.0.0" name="display at GBrowse"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="gbrowse" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -10,7 +10,7 @@
<dynamic_param name="site_organisms" value="4" split="True" separator="," /><!-- Filter out some of the links based upon matching site_id to a Galaxy application configuration parameter and by dataset dbkey -->
- <filter>${site_id in $APP.config.gbrowse_display_sites}</filter>
+ <filter>${site_id in $APP.datatypes_registry.get_display_sites('gbrowse')}</filter><filter>${dataset.dbkey in $site_dbkeys}</filter><!-- We define url and params as normal, but values defined in dynamic_param are available by specified name -->
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/igv/bam.xml
--- a/display_applications/igv/bam.xml
+++ b/display_applications/igv/bam.xml
@@ -2,7 +2,7 @@
<display id="igv_bam" version="1.0.0" name="display with IGV"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/igv/igv_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="igv" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -92,4 +92,4 @@
</display>
-<!-- Dan Blankenberg -->
\ No newline at end of file
+<!-- Dan Blankenberg -->
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/igv/vcf.xml
--- a/display_applications/igv/vcf.xml
+++ b/display_applications/igv/vcf.xml
@@ -2,7 +2,7 @@
<display id="igv_vcf" version="1.0.0" name="display with IGV"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/igv/igv_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="igv" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -92,4 +92,4 @@
</display>
-<!-- Dan Blankenberg -->
\ No newline at end of file
+<!-- Dan Blankenberg -->
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/rviewer/bed.xml
--- a/display_applications/rviewer/bed.xml
+++ b/display_applications/rviewer/bed.xml
@@ -2,7 +2,7 @@
<display id="rviewer_interval" version="1.0.0" name="display at RViewer"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/rviewer/rviewer_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="rviewer" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/rviewer/vcf.xml
--- a/display_applications/rviewer/vcf.xml
+++ b/display_applications/rviewer/vcf.xml
@@ -2,7 +2,7 @@
<display id="rviewer_vcf" version="1.0.0" name="display at RViewer"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/rviewer/rviewer_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="rviewer" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/ucsc/bam.xml
--- a/display_applications/ucsc/bam.xml
+++ b/display_applications/ucsc/bam.xml
@@ -1,6 +1,6 @@
<display id="ucsc_bam" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/ucsc/bigbed.xml
--- a/display_applications/ucsc/bigbed.xml
+++ b/display_applications/ucsc/bigbed.xml
@@ -1,6 +1,6 @@
<display id="ucsc_bigbed" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/ucsc/bigwig.xml
--- a/display_applications/ucsc/bigwig.xml
+++ b/display_applications/ucsc/bigwig.xml
@@ -1,6 +1,6 @@
<display id="ucsc_bigwig" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/ucsc/interval_as_bed.xml
--- a/display_applications/ucsc/interval_as_bed.xml
+++ b/display_applications/ucsc/interval_as_bed.xml
@@ -1,6 +1,6 @@
<display id="ucsc_interval_as_bed" version="1.0.0" name="display at UCSC" inherit="True"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/ucsc/maf_customtrack.xml
--- a/display_applications/ucsc/maf_customtrack.xml
+++ b/display_applications/ucsc/maf_customtrack.xml
@@ -1,6 +1,6 @@
<display id="ucsc_maf_customtrack" version="1.0.0" name="display at UCSC" inherit="True"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 display_applications/ucsc/vcf.xml
--- a/display_applications/ucsc/vcf.xml
+++ b/display_applications/ucsc/vcf.xml
@@ -1,6 +1,6 @@
<display id="ucsc_vcf" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 lib/galaxy/app.py
--- a/lib/galaxy/app.py
+++ b/lib/galaxy/app.py
@@ -64,9 +64,6 @@
# Load dbkey / genome build manager
self._configure_genome_builds( data_table_name="__dbkeys__", load_old_style=True )
- # Load build sites (old-style)
- self._configure_build_sites()
-
# Genomes
self.genomes = Genomes( self )
# Data providers registry.
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -16,7 +16,6 @@
from galaxy.util import string_as_bool
from galaxy.util import listify
from galaxy.util.dbkeys import GenomeBuilds
-from galaxy.util.build_sites import BuildSites
from galaxy import eggs
log = logging.getLogger( __name__ )
@@ -224,10 +223,6 @@
self.sanitize_all_html = string_as_bool( kwargs.get( 'sanitize_all_html', True ) )
self.serve_xss_vulnerable_mimetypes = string_as_bool( kwargs.get( 'serve_xss_vulnerable_mimetypes', False ) )
self.enable_old_display_applications = string_as_bool( kwargs.get( "enable_old_display_applications", "True" ) )
- self.ucsc_build_sites = resolve_path( kwargs.get( 'ucsc_build_sites', os.path.join( self.tool_data_path, 'shared', 'ucsc', 'ucsc_build_sites.txt') ), self.root )
- self.ucsc_display_sites = kwargs.get( 'ucsc_display_sites', "main,test,archaea,ucla" ).lower().split(",")
- self.gbrowse_build_sites = resolve_path( kwargs.get( 'gbrowse_build_sites', os.path.join( self.tool_data_path, 'shared', 'gbrowse', 'gbrowse_build_sites.txt') ), self.root )
- self.gbrowse_display_sites = kwargs.get( 'gbrowse_display_sites', "modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225" ).lower().split(",")
self.brand = kwargs.get( 'brand', None )
self.welcome_url = kwargs.get( 'welcome_url', '/static/welcome.html' )
# Configuration for the message box directly below the masthead.
@@ -575,9 +570,6 @@
def _configure_genome_builds( self, data_table_name="__dbkeys__", load_old_style=True ):
self.genome_builds = GenomeBuilds( self, data_table_name=data_table_name, load_old_style=load_old_style )
- def _configure_build_sites( self ):
- self.build_sites = BuildSites( self )
-
def _configure_toolbox( self ):
# Initialize the tools, making sure the list of tool configs includes the reserved migrated_tools_conf.xml file.
tool_configs = self.config.tool_configs
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 lib/galaxy/datatypes/display_applications/application.py
--- a/lib/galaxy/datatypes/display_applications/application.py
+++ b/lib/galaxy/datatypes/display_applications/application.py
@@ -81,9 +81,13 @@
class DynamicDisplayApplicationBuilder( object ):
@classmethod
- def __init__( self, elem, display_application ):
+ def __init__( self, elem, display_application, build_sites ):
rval = []
- filename = elem.get( 'from_file', None )
+ filename = None
+ if elem.get( 'site_type', None ) is not None:
+ filename = build_sites.get( elem.get( 'site_type' ) )
+ else:
+ filename = elem.get( 'from_file', None )
assert filename is not None, 'Filename and id attributes required for dynamic_links'
skip_startswith = elem.get( 'skip_startswith', None )
separator = elem.get( 'separator', '\t' )
@@ -176,7 +180,7 @@
if link:
rval.links[ link.id ] = link
for dynamic_links in elem.findall( 'dynamic_links' ):
- for link in DynamicDisplayApplicationBuilder( dynamic_links, rval ):
+ for link in DynamicDisplayApplicationBuilder( dynamic_links, rval, datatypes_registry.build_sites ):
rval.links[ link.id ] = link
return rval
def __init__( self, display_id, name, datatypes_registry, version = None ):
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 lib/galaxy/datatypes/genetics.py
--- a/lib/galaxy/datatypes/genetics.py
+++ b/lib/galaxy/datatypes/genetics.py
@@ -83,29 +83,31 @@
ret_val = []
ggtail = 'hgGenome_doSubmitUpload=submit'
if not dataset.dbkey:
- dataset.dbkey = 'hg18' # punt!
+ dataset.dbkey = 'hg18' # punt!
if dataset.has_data():
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build(dataset.dbkey):
- if site_name in app.config.ucsc_display_sites:
- site_url = site_url.replace('/hgTracks?','/hgGenome?') # for genome graphs
- internal_url = "%s" % url_for( controller='dataset',
- dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name )
- display_url = "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type)
- display_url = urllib.quote_plus( display_url )
- # was display_url = urllib.quote_plus( "%s/display_as?id=%i&display_app=%s" % (base_url, dataset.id, type) )
- #redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop) )
- sl = ["%sdb=%s" % (site_url,dataset.dbkey ),]
- #sl.append("&hgt.customText=%s")
- sl.append("&hgGenome_dataSetName=%s&hgGenome_dataSetDescription=%s" % (dataset.name, 'GalaxyGG_data'))
- sl.append("&hgGenome_formatType=best guess&hgGenome_markerType=best guess")
- sl.append("&hgGenome_columnLabels=first row&hgGenome_maxVal=&hgGenome_labelVals=")
- sl.append("&hgGenome_doSubmitUpload=submit")
- sl.append("&hgGenome_maxGapToFill=25000000&hgGenome_uploadFile=%s" % display_url)
- s = ''.join(sl)
- s = urllib.quote_plus(s)
- redirect_url = s
- link = '%s?redirect_url=%s&display_url=%s' % ( internal_url, redirect_url, display_url )
- ret_val.append( (site_name, link) )
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey):
+ if site_name in datatypes_registry.get_display_sites('ucsc'):
+ site_url = site_url.replace('/hgTracks?','/hgGenome?') # for genome graphs
+ internal_url = "%s" % url_for( controller='dataset',
+ dataset_id=dataset.id,
+ action='display_at',
+ filename='ucsc_' + site_name )
+ display_url = "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type)
+ display_url = urllib.quote_plus( display_url )
+ # was display_url = urllib.quote_plus( "%s/display_as?id=%i&display_app=%s" % (base_url, dataset.id, type) )
+ #redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop) )
+ sl = ["%sdb=%s" % (site_url,dataset.dbkey ),]
+ #sl.append("&hgt.customText=%s")
+ sl.append("&hgGenome_dataSetName=%s&hgGenome_dataSetDescription=%s" % (dataset.name, 'GalaxyGG_data'))
+ sl.append("&hgGenome_formatType=best guess&hgGenome_markerType=best guess")
+ sl.append("&hgGenome_columnLabels=first row&hgGenome_maxVal=&hgGenome_labelVals=")
+ sl.append("&hgGenome_doSubmitUpload=submit")
+ sl.append("&hgGenome_maxGapToFill=25000000&hgGenome_uploadFile=%s" % display_url)
+ s = ''.join(sl)
+ s = urllib.quote_plus(s)
+ redirect_url = s
+ link = '%s?redirect_url=%s&display_url=%s' % ( internal_url, redirect_url, display_url )
+ ret_val.append( (site_name, link) )
return ret_val
def make_html_table( self, dataset, skipchars=[] ):
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 lib/galaxy/datatypes/interval.py
--- a/lib/galaxy/datatypes/interval.py
+++ b/lib/galaxy/datatypes/interval.py
@@ -234,8 +234,8 @@
# Filter UCSC sites to only those that are supported by this build and
# enabled.
valid_sites = [ ( name, url )
- for name, url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey )
- if name in app.config.ucsc_display_sites ]
+ for name, url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey )
+ if name in app.datatypes_registry.get_display_sites('ucsc') ]
if not valid_sites:
return []
# If there are any valid sites, we need to generate the estimated
@@ -750,8 +750,8 @@
ret_val = []
seqid, start, stop = self.get_estimated_display_viewport( dataset )
if seqid is not None:
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey ):
- if site_name in app.config.ucsc_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey ):
+ if site_name in app.datatypes_registry.get_display_sites('ucsc'):
redirect_url = urllib.quote_plus(
"%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" %
( site_url, dataset.dbkey, seqid, start, stop ) )
@@ -762,8 +762,8 @@
ret_val = []
seqid, start, stop = self.get_estimated_display_viewport( dataset )
if seqid is not None:
- for site_name, site_url in app.build_sites.get_gbrowse_sites_by_build( dataset.dbkey ):
- if site_name in app.config.gbrowse_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('gbrowse', dataset.dbkey ):
+ if site_name in app.datatypes_registry.get_display_sites('gbrowse'):
if seqid.startswith( 'chr' ) and len ( seqid ) > 3:
seqid = seqid[3:]
redirect_url = urllib.quote_plus( "%s/?q=%s:%s..%s&eurl=%%s" % ( site_url, seqid, start, stop ) )
@@ -1091,8 +1091,8 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport( dataset )
if chrom is not None:
- for site_name, site_url in app.build_sites.get_gbrowse_sites_by_build( dataset.dbkey ):
- if site_name in app.config.gbrowse_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('gbrowse', dataset.dbkey ):
+ if site_name in app.datatypes_registry.get_display_sites('gbrowse'):
if chrom.startswith( 'chr' ) and len ( chrom ) > 3:
chrom = chrom[3:]
redirect_url = urllib.quote_plus( "%s/?q=%s:%s..%s&eurl=%%s" % ( site_url, chrom, start, stop ) )
@@ -1103,8 +1103,8 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport( dataset )
if chrom is not None:
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey ):
- if site_name in app.config.ucsc_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey ):
+ if site_name in app.datatypes_registry.get_display_sites('ucsc'):
redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % ( site_url, dataset.dbkey, chrom, start, stop ) )
link = self._get_remote_call_url( redirect_url, site_name, dataset, type, app, base_url )
ret_val.append( ( site_name, link ) )
@@ -1285,8 +1285,8 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport(dataset)
if chrom is not None:
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build(dataset.dbkey):
- if site_name in app.config.ucsc_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc',dataset.dbkey):
+ if site_name in app.datatypes_registry.get_display_sites('ucsc'):
internal_url = "%s" % url_for( controller='dataset', dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name )
display_url = urllib.quote_plus( "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type) )
redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop ) )
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -69,6 +69,10 @@
self.datatype_elems = []
self.sniffer_elems = []
self.xml_filename = None
+ # Build sites
+ self.build_sites = {}
+ self.display_sites = {}
+ self.legacy_build_sites = {}
def load_datatypes( self, root_dir=None, config=None, deactivate=False, override=True ):
"""
@@ -285,6 +289,8 @@
handling_proprietary_datatypes=handling_proprietary_datatypes,
override=override )
self.upload_file_formats.sort()
+ # Load build sites
+ self.load_build_sites( root )
# Persist the xml form of the registry into a temporary file so that it can be loaded from the command line by tools and
# set_metadata processing.
self.to_xml_file()
@@ -303,6 +309,34 @@
self.sniff_order.append( datatype )
append_to_sniff_order()
+ def load_build_sites( self, root ):
+ if root.find( 'build_sites' ):
+ for elem in root.find( 'build_sites' ).findall( 'site' ):
+ if not (elem.get( 'type' ) and elem.get( 'file' )):
+ self.log.exception( "Site is missing required 'type' and 'file' attributes: %s" )
+ else:
+ site_type = elem.get( 'type' )
+ file = elem.get( 'file' )
+ self.build_sites[site_type] = file
+ if site_type in ('ucsc', 'gbrowse'):
+ self.legacy_build_sites[site_type] = galaxy.util.read_build_sites( file )
+ if elem.get( 'display', None ):
+ display = elem.get( 'display' )
+ self.display_sites[site_type] = [ x.strip() for x in display.lower().split( ',' ) ]
+ self.log.debug( "Loaded build site '%s': %s with display sites: %s", site_type, file, display )
+ else:
+ self.log.debug( "Loaded build site '%s': %s", site_type, file )
+
+ def get_legacy_sites_by_build( self, site_type, build ):
+ sites = []
+ for site in self.legacy_build_sites.get(site_type, []):
+ if build in site['builds']:
+ sites.append((site['name'], site['url']))
+ return sites
+
+ def get_display_sites( self, site_type ):
+ return self.display_sites.get( site_type, [] )
+
def load_datatype_sniffers( self, root, deactivate=False, handling_proprietary_datatypes=False, override=False ):
"""
Process the sniffers element from a parsed a datatypes XML file located at root_dir/config (if processing the Galaxy
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 lib/galaxy/util/__init__.py
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -844,6 +844,30 @@
return db_names
+def read_build_sites( filename, check_builds=True ):
+ """ read db names to ucsc mappings from file, this file should probably be merged with the one above """
+ build_sites = []
+ try:
+ for line in open(filename):
+ try:
+ if line[0:1] == "#":
+ continue
+ fields = line.replace("\r", "").replace("\n", "").split("\t")
+ site_name = fields[0]
+ site = fields[1]
+ if check_builds:
+ site_builds = fields[2].split(",")
+ site_dict = {'name': site_name, 'url': site, 'builds': site_builds}
+ else:
+ site_dict = {'name': site_name, 'url': site}
+ build_sites.append( site_dict )
+ except:
+ continue
+ except:
+ print "ERROR: Unable to read builds for site file %s" % filename
+ return build_sites
+
+
def relativize_symlinks( path, start=None, followlinks=False):
for root, dirs, files in os.walk( path, followlinks=followlinks ):
rel_start = None
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 lib/galaxy/util/build_sites.py
--- a/lib/galaxy/util/build_sites.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
-Functionality for dealing with build sites for legacy display applications.
-"""
-import os.path
-
-
-class BuildSites( object ):
-
- def __init__( self, app ):
- self._app = app
- self._build_sites = {}
- self.load_build_sites()
-
- def read_build_sites( self, filename, check_builds=True ):
- """ read db names to ucsc mappings from file, this file should probably be merged with the one above """
- build_sites = []
- try:
- for line in open(filename):
- try:
- if line[0:1] == "#":
- continue
- fields = line.replace("\r", "").replace("\n", "").split("\t")
- site_name = fields[0]
- site = fields[1]
- if check_builds:
- site_builds = fields[2].split(",")
- site_dict = {'name': site_name, 'url': site, 'builds': site_builds}
- else:
- site_dict = {'name': site_name, 'url': site}
- build_sites.append( site_dict )
- except:
- continue
- except:
- print "ERROR: Unable to read builds for site file %s" % filename
- return build_sites
-
- def load_build_sites( self ):
- self._build_sites['ucsc'] = self.read_build_sites( self._app.config.ucsc_build_sites )
- self._build_sites['gbrowse'] = self.read_build_sites( self._app.config.gbrowse_build_sites )
-
- def _get_site_by_build( self, site_type, build ):
- sites = []
- for site in self._build_sites[site_type]:
- if build in site['builds']:
- sites.append((site['name'], site['url']))
- return sites
-
- def get_ucsc_sites_by_build( self, build ):
- return self._get_site_by_build( 'ucsc', build )
-
- def get_gbrowse_sites_by_build( self, build ):
- return self._get_site_by_build( 'gbrowse', build )
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 lib/galaxy/web/framework/webapp.py
--- a/lib/galaxy/web/framework/webapp.py
+++ b/lib/galaxy/web/framework/webapp.py
@@ -429,7 +429,7 @@
url_for( controller='dataset', action='list' )
)
display_as = url_for( controller='root', action='display_as' )
- if self.app.config.ucsc_display_sites and self.request.path == display_as:
+ if self.app.datatypes_registry.get_display_sites('ucsc') and self.request.path == display_as:
try:
host = socket.gethostbyaddr( self.environ[ 'REMOTE_ADDR' ] )[0]
except( socket.error, socket.herror, socket.gaierror, socket.timeout ):
diff -r 404c21fa0de76e20deba7b63eebc15cca617173c -r e6546f41d7b2a11dbf26246662a55c6e86495269 universe_wsgi.ini.sample
--- a/universe_wsgi.ini.sample
+++ b/universe_wsgi.ini.sample
@@ -364,14 +364,6 @@
# which browsers should be available. URLs and builds available at these
# browsers are defined in the specifield files.
-# UCSC browsers:
-#ucsc_build_sites = tool-data/shared/ucsc/ucsc_build_sites.txt
-#ucsc_display_sites = main,test,archaea,ucla
-
-# GBrowse servers:
-#gbrowse_build_sites = tool-data/shared/gbrowse/gbrowse_build_sites.txt
-#gbrowse_display_sites = modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225
-
# If use_remote_user = True, display application servers will be denied access
# to Galaxy and so displaying datasets in these sites will fail.
# display_servers contains a list of hostnames which should be allowed to
https://bitbucket.org/galaxy/galaxy-central/commits/83e45a9e4cc9/
Changeset: 83e45a9e4cc9
User: dannon
Date: 2014-09-04 22:30:50
Summary: Merged in carlfeberhard/galaxy-central-fork0 (pull request #484)
Break up web/framework/__init__ into more focused modules
Affected #: 20 files
diff -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f -r 83e45a9e4cc994daed44e406a334660d9db7ed30 lib/galaxy/datatypes/metadata.py
--- a/lib/galaxy/datatypes/metadata.py
+++ b/lib/galaxy/datatypes/metadata.py
@@ -6,7 +6,6 @@
import copy
import cPickle
import json
-import logging
import os
import shutil
import sys
@@ -15,13 +14,19 @@
from os.path import abspath
-import galaxy.model
-from galaxy.util import listify, stringify_dictionary_keys, string_as_bool
-from galaxy.util.odict import odict
-from galaxy.util import in_directory
-from galaxy.web import form_builder
+from galaxy import eggs
+eggs.require( "SQLAlchemy >= 0.4" )
from sqlalchemy.orm import object_session
+import galaxy.model
+from galaxy.util import listify
+from galaxy.util import stringify_dictionary_keys
+from galaxy.util import string_as_bool
+from galaxy.util import in_directory
+from galaxy.util.odict import odict
+from galaxy.web import form_builder
+
+import logging
log = logging.getLogger(__name__)
STATEMENTS = "__galaxy_statements__" #this is the name of the property in a Datatype class where new metadata spec element Statements are stored
diff -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f -r 83e45a9e4cc994daed44e406a334660d9db7ed30 lib/galaxy/managers/context.py
--- /dev/null
+++ b/lib/galaxy/managers/context.py
@@ -0,0 +1,179 @@
+"""
+Mixins for transaction-like objects.
+"""
+
+import os
+
+from galaxy.util.json import to_json_string
+from galaxy.util import bunch
+
+class ProvidesAppContext( object ):
+ """ For transaction-like objects to provide Galaxy convience layer for
+ database and event handling.
+
+ Mixed in class must provide `app` property.
+ """
+
+ def log_action( self, user=None, action=None, context=None, params=None):
+ """
+ Application-level logging of user actions.
+ """
+ if self.app.config.log_actions:
+ action = self.app.model.UserAction(action=action, context=context, params=unicode( to_json_string( params ) ) )
+ try:
+ if user:
+ action.user = user
+ else:
+ action.user = self.user
+ except:
+ action.user = None
+ try:
+ action.session_id = self.galaxy_session.id
+ except:
+ action.session_id = None
+ self.sa_session.add( action )
+ self.sa_session.flush()
+
+ def log_event( self, message, tool_id=None, **kwargs ):
+ """
+ Application level logging. Still needs fleshing out (log levels and such)
+ Logging events is a config setting - if False, do not log.
+ """
+ if self.app.config.log_events:
+ event = self.app.model.Event()
+ event.tool_id = tool_id
+ try:
+ event.message = message % kwargs
+ except:
+ event.message = message
+ try:
+ event.history = self.get_history()
+ except:
+ event.history = None
+ try:
+ event.history_id = self.history.id
+ except:
+ event.history_id = None
+ try:
+ event.user = self.user
+ except:
+ event.user = None
+ try:
+ event.session_id = self.galaxy_session.id
+ except:
+ event.session_id = None
+ self.sa_session.add( event )
+ self.sa_session.flush()
+
+ @property
+ def sa_session( self ):
+ """
+ Returns a SQLAlchemy session -- currently just gets the current
+ session from the threadlocal session context, but this is provided
+ to allow migration toward a more SQLAlchemy 0.4 style of use.
+ """
+ return self.app.model.context.current
+
+ def expunge_all( self ):
+ app = self.app
+ context = app.model.context
+ context.expunge_all()
+ # This is a bit hacky, should refctor this. Maybe refactor to app -> expunge_all()
+ if hasattr(app, 'install_model'):
+ install_model = app.install_model
+ if install_model != app.model:
+ install_model.context.expunge_all()
+
+ def get_toolbox(self):
+ """Returns the application toolbox"""
+ return self.app.toolbox
+
+ @property
+ def model( self ):
+ return self.app.model
+
+ @property
+ def install_model( self ):
+ return self.app.install_model
+
+ def request_types(self):
+ if self.sa_session.query( self.app.model.RequestType ).filter_by( deleted=False ).count() > 0:
+ return True
+ return False
+
+
+class ProvidesUserContext( object ):
+ """ For transaction-like objects to provide Galaxy convience layer for
+ reasoning about users.
+
+ Mixed in class must provide `user`, `api_inherit_admin`, and `app`
+ properties.
+ """
+
+ @property
+ def anonymous( self ):
+ return self.user is None and not self.api_inherit_admin
+
+ def get_current_user_roles( self ):
+ user = self.user
+ if user:
+ roles = user.all_roles()
+ else:
+ roles = []
+ return roles
+
+ def user_is_admin( self ):
+ if self.api_inherit_admin:
+ return True
+ return self.user and self.user.email in self.app.config.admin_users_list
+
+ def user_can_do_run_as( self ):
+ run_as_users = [ user for user in self.app.config.get( "api_allow_run_as", "" ).split( "," ) if user ]
+ if not run_as_users:
+ return False
+ user_in_run_as_users = self.user and self.user.email in run_as_users
+ # Can do if explicitly in list or master_api_key supplied.
+ can_do_run_as = user_in_run_as_users or self.api_inherit_admin
+ return can_do_run_as
+
+ @property
+ def user_ftp_dir( self ):
+ identifier = self.app.config.ftp_upload_dir_identifier
+ return os.path.join( self.app.config.ftp_upload_dir, getattr( self.user, identifier ) )
+
+
+class ProvidesHistoryContext( object ):
+ """ For transaction-like objects to provide Galaxy convience layer for
+ reasoning about histories.
+
+ Mixed in class must provide `user`, `history`, and `app`
+ properties.
+ """
+
+ def db_dataset_for( self, dbkey ):
+ """
+ Returns the db_file dataset associated/needed by `dataset`, or `None`.
+ """
+ # If no history, return None.
+ if self.history is None:
+ return None
+#TODO: when does this happen? is it Bunch or util.bunch.Bunch?
+ if isinstance( self.history, bunch.Bunch ):
+ # The API presents a Bunch for a history. Until the API is
+ # more fully featured for handling this, also return None.
+ return None
+ datasets = self.sa_session.query( self.app.model.HistoryDatasetAssociation ) \
+ .filter_by( deleted=False, history_id=self.history.id, extension="len" )
+ for ds in datasets:
+ if dbkey == ds.dbkey:
+ return ds
+ return None
+
+ @property
+ def db_builds( self ):
+ """
+ Returns the builds defined by galaxy and the builds defined by
+ the user (chromInfo in history).
+ """
+ # FIXME: This method should be removed
+ return self.app.genome_builds.get_genome_build_names( trans=self )
diff -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f -r 83e45a9e4cc994daed44e406a334660d9db7ed30 lib/galaxy/web/__init__.py
--- a/lib/galaxy/web/__init__.py
+++ b/lib/galaxy/web/__init__.py
@@ -1,23 +1,25 @@
"""
The Galaxy web application framework
"""
-from framework import expose
-from framework import json
-from framework import json_pretty
-from framework import require_login
-from framework import require_admin
from framework import url_for
-from framework import error
-from framework import form
-from framework import FormBuilder
-from framework import expose_api
-from framework import expose_api_anonymous
-from framework import expose_api_raw
-from framework import expose_api_raw_anonymous
-from framework.base import httpexceptions
+from framework.decorators import error
+from framework.decorators import expose
+from framework.decorators import json
+from framework.decorators import json_pretty
+from framework.decorators import require_login
+from framework.decorators import require_admin
+from framework.decorators import expose_api
+from framework.decorators import expose_api_anonymous
+from framework.decorators import expose_api_raw
+from framework.decorators import expose_api_raw_anonymous
# TODO: Drop and make these the default.
-from framework import _future_expose_api
-from framework import _future_expose_api_anonymous
-from framework import _future_expose_api_raw
-from framework import _future_expose_api_raw_anonymous
+from framework.decorators import _future_expose_api
+from framework.decorators import _future_expose_api_anonymous
+from framework.decorators import _future_expose_api_raw
+from framework.decorators import _future_expose_api_raw_anonymous
+
+from framework.formbuilder import form
+from framework.formbuilder import FormBuilder
+
+from framework.base import httpexceptions
diff -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f -r 83e45a9e4cc994daed44e406a334660d9db7ed30 lib/galaxy/web/base/pluginframework.py
--- a/lib/galaxy/web/base/pluginframework.py
+++ b/lib/galaxy/web/base/pluginframework.py
@@ -579,7 +579,7 @@
"""
Pass control over to trans and render ``template_filename``.
- :type trans: ``galaxy.web.framework.GalaxyWebTransaction``
+ :type trans: ``galaxy.web.framework.webapp.GalaxyWebTransaction``
:param trans: transaction doing the rendering
:type plugin: ``util.bunch.Bunch``
:param plugin: the plugin containing the template to render
diff -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f -r 83e45a9e4cc994daed44e406a334660d9db7ed30 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -2,1395 +2,9 @@
Galaxy web application framework
"""
-import hashlib
-import inspect
-import os
-import random
-import socket
-import string
-import time
-from traceback import format_exc
-from Cookie import CookieError
-from functools import wraps
-
from galaxy import eggs
-
-eggs.require( "Cheetah" )
-from Cheetah.Template import Template
-
-from galaxy import util
-from galaxy.exceptions import error_codes
-from galaxy.exceptions import MessageException
-from galaxy.util import asbool
-from galaxy.util import safe_str_cmp
-from galaxy.util.backports.importlib import import_module
-from galaxy.util.json import from_json_string, to_json_string
-from galaxy.util.sanitize_html import sanitize_html
-from galaxy.web.framework import base, helpers
-
-import paste.httpexceptions
-
-eggs.require( "Mako" )
-import mako.template
-import mako.lookup
-import mako.runtime
-
-eggs.require( "pytz" ) # Used by Babel.
-eggs.require( "Babel" )
-from babel.support import Translations
-from babel import Locale
-
-eggs.require( "SQLAlchemy >= 0.4" )
-from sqlalchemy import and_
-from sqlalchemy.orm.exc import NoResultFound
-
eggs.require( "pexpect" )
eggs.require( "amqp" )
-import logging
-log = logging.getLogger( __name__ )
-
+import base
url_for = base.routes.url_for
-
-UCSC_SERVERS = (
- 'hgw1.cse.ucsc.edu',
- 'hgw2.cse.ucsc.edu',
- 'hgw3.cse.ucsc.edu',
- 'hgw4.cse.ucsc.edu',
- 'hgw5.cse.ucsc.edu',
- 'hgw6.cse.ucsc.edu',
- 'hgw7.cse.ucsc.edu',
- 'hgw8.cse.ucsc.edu',
-)
-
-JSON_CONTENT_TYPE = "application/json"
-
-
-# ----------------------------------------------------------------------------- web controller decorators
-def expose( func ):
- """
- Decorator: mark a function as 'exposed' and thus web accessible
- """
- func.exposed = True
- return func
-
-def json( func, **json_kwargs ):
- """
- Format the response as JSON and set the response content type to
- JSON_CONTENT_TYPE.
- """
- @wraps(func)
- def call_and_format( self, trans, *args, **kwargs ):
- trans.response.set_content_type( JSON_CONTENT_TYPE )
- return to_json_string( func( self, trans, *args, **kwargs ), **json_kwargs )
- if not hasattr(func, '_orig'):
- call_and_format._orig = func
- call_and_format.exposed = True
- return call_and_format
-
-def json_pretty( func ):
- """
- Indent and sort returned JSON.
- """
- return json( func, indent=4, sort_keys=True )
-
-def require_login( verb="perform this action", use_panels=False, webapp='galaxy' ):
- def argcatcher( func ):
- @wraps(func)
- def decorator( self, trans, *args, **kwargs ):
- if trans.get_user():
- return func( self, trans, *args, **kwargs )
- else:
- return trans.show_error_message(
- 'You must be <a target="galaxy_main" href="%s">logged in</a> to %s.'
- % ( url_for( controller='user', action='login', webapp=webapp ), verb ), use_panels=use_panels )
- return decorator
- return argcatcher
-
-def require_admin( func ):
- @wraps(func)
- def decorator( self, trans, *args, **kwargs ):
- if not trans.user_is_admin():
- msg = "You must be an administrator to access this feature."
- user = trans.get_user()
- if not trans.app.config.admin_users_list:
- msg = "You must be logged in as an administrator to access this feature, but no administrators are set in the Galaxy configuration."
- elif not user:
- msg = "You must be logged in as an administrator to access this feature."
- trans.response.status = 403
- if trans.response.get_content_type() == 'application/json':
- return msg
- else:
- return trans.show_error_message( msg )
- return func( self, trans, *args, **kwargs )
- return decorator
-
-
-# ----------------------------------------------------------------------------- (original) api decorators
-def expose_api( func, to_json=True, user_required=True ):
- """
- Expose this function via the API.
- """
- @wraps(func)
- def decorator( self, trans, *args, **kwargs ):
- def error( environ, start_response ):
- start_response( error_status, [('Content-type', 'text/plain')] )
- return error_message
- error_status = '403 Forbidden'
- if trans.error_message:
- return trans.error_message
- if user_required and trans.anonymous:
- error_message = "API Authentication Required for this request"
- return error
- if trans.request.body:
- try:
- kwargs['payload'] = __extract_payload_from_request(trans, func, kwargs)
- except ValueError:
- error_status = '400 Bad Request'
- error_message = 'Your request did not appear to be valid JSON, please consult the API documentation'
- return error
- trans.response.set_content_type( "application/json" )
- # send 'do not cache' headers to handle IE's caching of ajax get responses
- trans.response.headers[ 'Cache-Control' ] = "max-age=0,no-cache,no-store"
- # Perform api_run_as processing, possibly changing identity
- if 'payload' in kwargs and 'run_as' in kwargs['payload']:
- if not trans.user_can_do_run_as():
- error_message = 'User does not have permissions to run jobs as another user'
- return error
- try:
- decoded_user_id = trans.security.decode_id( kwargs['payload']['run_as'] )
- except TypeError:
- trans.response.status = 400
- return "Malformed user id ( %s ) specified, unable to decode." % str( kwargs['payload']['run_as'] )
- try:
- user = trans.sa_session.query( trans.app.model.User ).get( decoded_user_id )
- trans.api_inherit_admin = trans.user_is_admin()
- trans.set_user(user)
- except:
- trans.response.status = 400
- return "That user does not exist."
- try:
- rval = func( self, trans, *args, **kwargs)
- if to_json and trans.debug:
- rval = to_json_string( rval, indent=4, sort_keys=True )
- elif to_json:
- rval = to_json_string( rval )
- return rval
- except paste.httpexceptions.HTTPException:
- raise # handled
- except:
- log.exception( 'Uncaught exception in exposed API method:' )
- raise paste.httpexceptions.HTTPServerError()
- if not hasattr(func, '_orig'):
- decorator._orig = func
- decorator.exposed = True
- return decorator
-
-def __extract_payload_from_request(trans, func, kwargs):
- content_type = trans.request.headers['content-type']
- if content_type.startswith('application/x-www-form-urlencoded') or content_type.startswith('multipart/form-data'):
- # If the content type is a standard type such as multipart/form-data, the wsgi framework parses the request body
- # and loads all field values into kwargs. However, kwargs also contains formal method parameters etc. which
- # are not a part of the request body. This is a problem because it's not possible to differentiate between values
- # which are a part of the request body, and therefore should be a part of the payload, and values which should not be
- # in the payload. Therefore, the decorated method's formal arguments are discovered through reflection and removed from
- # the payload dictionary. This helps to prevent duplicate argument conflicts in downstream methods.
- payload = kwargs.copy()
- named_args, _, _, _ = inspect.getargspec(func)
- for arg in named_args:
- payload.pop(arg, None)
- for k, v in payload.iteritems():
- if isinstance(v, (str, unicode)):
- try:
- payload[k] = from_json_string(v)
- except:
- # may not actually be json, just continue
- pass
- payload = util.recursively_stringify_dictionary_keys( payload )
- else:
- # Assume application/json content type and parse request body manually, since wsgi won't do it. However, the order of this check
- # should ideally be in reverse, with the if clause being a check for application/json and the else clause assuming a standard encoding
- # such as multipart/form-data. Leaving it as is for backward compatibility, just in case.
- payload = util.recursively_stringify_dictionary_keys( from_json_string( trans.request.body ) )
- return payload
-
-def expose_api_raw( func ):
- """
- Expose this function via the API but don't dump the results
- to JSON.
- """
- return expose_api( func, to_json=False )
-
-def expose_api_raw_anonymous( func ):
- """
- Expose this function via the API but don't dump the results
- to JSON.
- """
- return expose_api( func, to_json=False, user_required=False )
-
-def expose_api_anonymous( func, to_json=True ):
- """
- Expose this function via the API but don't require a set user.
- """
- return expose_api( func, to_json=to_json, user_required=False )
-
-
-# ----------------------------------------------------------------------------- (new) api decorators
-# TODO: rename as expose_api and make default.
-def _future_expose_api( func, to_json=True, user_required=True ):
- """
- Expose this function via the API.
- """
- @wraps(func)
- def decorator( self, trans, *args, **kwargs ):
- if trans.error_message:
- # TODO: Document this branch, when can this happen,
- # I don't understand it.
- return __api_error_response( trans, err_msg=trans.error_message )
- if user_required and trans.anonymous:
- error_code = error_codes.USER_NO_API_KEY
- # Use error codes default error message.
- err_msg = "API authentication required for this request"
- return __api_error_response( trans, err_code=error_code, err_msg=err_msg, status_code=403 )
- if trans.request.body:
- try:
- kwargs['payload'] = __extract_payload_from_request(trans, func, kwargs)
- except ValueError:
- error_code = error_codes.USER_INVALID_JSON
- return __api_error_response( trans, status_code=400, err_code=error_code )
-
- trans.response.set_content_type( JSON_CONTENT_TYPE )
- # send 'do not cache' headers to handle IE's caching of ajax get responses
- trans.response.headers[ 'Cache-Control' ] = "max-age=0,no-cache,no-store"
- # TODO: Refactor next block out into a helper procedure.
- # Perform api_run_as processing, possibly changing identity
- if 'payload' in kwargs and 'run_as' in kwargs['payload']:
- if not trans.user_can_do_run_as():
- error_code = error_codes.USER_CANNOT_RUN_AS
- return __api_error_response( trans, err_code=error_code, status_code=403 )
- try:
- decoded_user_id = trans.security.decode_id( kwargs['payload']['run_as'] )
- except TypeError:
- error_message = "Malformed user id ( %s ) specified, unable to decode." % str( kwargs['payload']['run_as'] )
- error_code = error_codes.USER_INVALID_RUN_AS
- return __api_error_response( trans, err_code=error_code, err_msg=error_message, status_code=400)
- try:
- user = trans.sa_session.query( trans.app.model.User ).get( decoded_user_id )
- trans.api_inherit_admin = trans.user_is_admin()
- trans.set_user(user)
- except:
- error_code = error_codes.USER_INVALID_RUN_AS
- return __api_error_response( trans, err_code=error_code, status_code=400 )
- try:
- rval = func( self, trans, *args, **kwargs)
- if to_json and trans.debug:
- rval = to_json_string( rval, indent=4, sort_keys=True )
- elif to_json:
- rval = to_json_string( rval )
- return rval
- except MessageException as e:
- traceback_string = format_exc()
- return __api_error_response( trans, exception=e, traceback=traceback_string )
- except paste.httpexceptions.HTTPException:
- # TODO: Allow to pass or format for the API???
- raise # handled
- except Exception as e:
- traceback_string = format_exc()
- error_message = 'Uncaught exception in exposed API method:'
- log.exception( error_message )
- return __api_error_response(
- trans,
- status_code=500,
- exception=e,
- traceback=traceback_string,
- err_msg=error_message,
- err_code=error_codes.UNKNOWN
- )
- if not hasattr(func, '_orig'):
- decorator._orig = func
- decorator.exposed = True
- return decorator
-
-def __api_error_message( trans, **kwds ):
- exception = kwds.get( "exception", None )
- if exception:
- # If we are passed a MessageException use err_msg.
- default_error_code = getattr( exception, "err_code", error_codes.UNKNOWN )
- default_error_message = getattr( exception, "err_msg", default_error_code.default_error_message )
- extra_error_info = getattr( exception, 'extra_error_info', {} )
- if not isinstance( extra_error_info, dict ):
- extra_error_info = {}
- else:
- default_error_message = "Error processing API request."
- default_error_code = error_codes.UNKNOWN
- extra_error_info = {}
- traceback_string = kwds.get( "traceback", "No traceback available." )
- err_msg = kwds.get( "err_msg", default_error_message )
- error_code_object = kwds.get( "err_code", default_error_code )
- try:
- error_code = error_code_object.code
- except AttributeError:
- # Some sort of bad error code sent in, logic failure on part of
- # Galaxy developer.
- error_code = error_codes.UNKNOWN.code
- # Would prefer the terminology of error_code and error_message, but
- # err_msg used a good number of places already. Might as well not change
- # it?
- error_response = dict( err_msg=err_msg, err_code=error_code, **extra_error_info )
- if trans.debug: # TODO: Should admins get to see traceback as well?
- error_response[ "traceback" ] = traceback_string
- return error_response
-
-def __api_error_response( trans, **kwds ):
- error_dict = __api_error_message( trans, **kwds )
- exception = kwds.get( "exception", None )
- # If we are given an status code directly - use it - otherwise check
- # the exception for a status_code attribute.
- if "status_code" in kwds:
- status_code = int( kwds.get( "status_code" ) )
- elif hasattr( exception, "status_code" ):
- status_code = int( exception.status_code )
- else:
- status_code = 500
- response = trans.response
- if not response.status or str(response.status).startswith("20"):
- # Unset status code appears to be string '200 OK', if anything
- # non-success (i.e. not 200 or 201) has been set, do not override
- # underlying controller.
- response.status = status_code
- return to_json_string( error_dict )
-
-
-# TODO: rename as expose_api and make default.
-def _future_expose_api_anonymous( func, to_json=True ):
- """
- Expose this function via the API but don't require a set user.
- """
- return _future_expose_api( func, to_json=to_json, user_required=False )
-
-
-def _future_expose_api_raw( func ):
- return _future_expose_api( func, to_json=False, user_required=True )
-
-
-def _future_expose_api_raw_anonymous( func ):
- return _future_expose_api( func, to_json=False, user_required=False )
-
-
-
-NOT_SET = object()
-
-
-def error( message ):
- raise MessageException( message, type='error' )
-
-
-def form( *args, **kwargs ):
- return FormBuilder( *args, **kwargs )
-
-
-class WebApplication( base.WebApplication ):
-
- def __init__( self, galaxy_app, session_cookie='galaxysession', name=None ):
- self.name = name
- base.WebApplication.__init__( self )
- self.set_transaction_factory( lambda e: self.transaction_chooser( e, galaxy_app, session_cookie ) )
- # Mako support
- self.mako_template_lookup = self.create_mako_template_lookup( galaxy_app, name )
- # Security helper
- self.security = galaxy_app.security
-
- def create_mako_template_lookup( self, galaxy_app, name ):
- paths = []
- # First look in webapp specific directory
- if name is not None:
- paths.append( os.path.join( galaxy_app.config.template_path, 'webapps', name ) )
- # Then look in root directory
- paths.append( galaxy_app.config.template_path )
- # Create TemplateLookup with a small cache
- return mako.lookup.TemplateLookup(directories=paths,
- module_directory=galaxy_app.config.template_cache,
- collection_size=500,
- output_encoding='utf-8' )
-
- def handle_controller_exception( self, e, trans, **kwargs ):
- if isinstance( e, MessageException ):
- # In the case of a controller exception, sanitize to make sure
- # unsafe html input isn't reflected back to the user
- return trans.show_message( sanitize_html(e.err_msg), e.type )
-
- def make_body_iterable( self, trans, body ):
- if isinstance( body, FormBuilder ):
- body = trans.show_form( body )
- return base.WebApplication.make_body_iterable( self, trans, body )
-
- def transaction_chooser( self, environ, galaxy_app, session_cookie ):
- return GalaxyWebTransaction( environ, galaxy_app, self, session_cookie )
-
- def add_ui_controllers( self, package_name, app ):
- """
- Search for UI controllers in `package_name` and add
- them to the webapp.
- """
- from galaxy.web.base.controller import BaseUIController
- from galaxy.web.base.controller import ControllerUnavailable
- package = import_module( package_name )
- controller_dir = package.__path__[0]
- for fname in os.listdir( controller_dir ):
- if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
- name = fname[:-3]
- module_name = package_name + "." + name
- try:
- module = import_module( module_name )
- except ControllerUnavailable, exc:
- log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
- continue
- # Look for a controller inside the modules
- for key in dir( module ):
- T = getattr( module, key )
- if inspect.isclass( T ) and T is not BaseUIController and issubclass( T, BaseUIController ):
- controller = self._instantiate_controller( T, app )
- self.add_ui_controller( name, controller )
-
- def add_api_controllers( self, package_name, app ):
- """
- Search for UI controllers in `package_name` and add
- them to the webapp.
- """
- from galaxy.web.base.controller import BaseAPIController
- from galaxy.web.base.controller import ControllerUnavailable
- package = import_module( package_name )
- controller_dir = package.__path__[0]
- for fname in os.listdir( controller_dir ):
- if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
- name = fname[:-3]
- module_name = package_name + "." + name
- try:
- module = import_module( module_name )
- except ControllerUnavailable, exc:
- log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
- continue
- for key in dir( module ):
- T = getattr( module, key )
- # Exclude classes such as BaseAPIController and BaseTagItemsController
- if inspect.isclass( T ) and not key.startswith("Base") and issubclass( T, BaseAPIController ):
- # By default use module_name, but allow controller to override name
- controller_name = getattr( T, "controller_name", name )
- controller = self._instantiate_controller( T, app )
- self.add_api_controller( controller_name, controller )
-
- def _instantiate_controller( self, T, app ):
- """ Extension point, allow apps to contstruct controllers differently,
- really just used to stub out actual controllers for routes testing.
- """
- return T( app )
-
-
-class ProvidesAppContext( object ):
- """ For transaction-like objects to provide Galaxy convience layer for
- database and event handling.
-
- Mixed in class must provide `app` property.
- """
-
- def log_action( self, user=None, action=None, context=None, params=None):
- """
- Application-level logging of user actions.
- """
- if self.app.config.log_actions:
- action = self.app.model.UserAction(action=action, context=context, params=unicode( to_json_string( params ) ) )
- try:
- if user:
- action.user = user
- else:
- action.user = self.user
- except:
- action.user = None
- try:
- action.session_id = self.galaxy_session.id
- except:
- action.session_id = None
- self.sa_session.add( action )
- self.sa_session.flush()
-
- def log_event( self, message, tool_id=None, **kwargs ):
- """
- Application level logging. Still needs fleshing out (log levels and such)
- Logging events is a config setting - if False, do not log.
- """
- if self.app.config.log_events:
- event = self.app.model.Event()
- event.tool_id = tool_id
- try:
- event.message = message % kwargs
- except:
- event.message = message
- try:
- event.history = self.get_history()
- except:
- event.history = None
- try:
- event.history_id = self.history.id
- except:
- event.history_id = None
- try:
- event.user = self.user
- except:
- event.user = None
- try:
- event.session_id = self.galaxy_session.id
- except:
- event.session_id = None
- self.sa_session.add( event )
- self.sa_session.flush()
-
- @property
- def sa_session( self ):
- """
- Returns a SQLAlchemy session -- currently just gets the current
- session from the threadlocal session context, but this is provided
- to allow migration toward a more SQLAlchemy 0.4 style of use.
- """
- return self.app.model.context.current
-
- def expunge_all( self ):
- app = self.app
- context = app.model.context
- context.expunge_all()
- # This is a bit hacky, should refctor this. Maybe refactor to app -> expunge_all()
- if hasattr(app, 'install_model'):
- install_model = app.install_model
- if install_model != app.model:
- install_model.context.expunge_all()
-
- def get_toolbox(self):
- """Returns the application toolbox"""
- return self.app.toolbox
-
- @property
- def model( self ):
- return self.app.model
-
- @property
- def install_model( self ):
- return self.app.install_model
-
- def request_types(self):
- if self.sa_session.query( self.app.model.RequestType ).filter_by( deleted=False ).count() > 0:
- return True
- return False
-
-
-class ProvidesUserContext( object ):
- """ For transaction-like objects to provide Galaxy convience layer for
- reasoning about users.
-
- Mixed in class must provide `user`, `api_inherit_admin`, and `app`
- properties.
- """
-
- @property
- def anonymous( self ):
- return self.user is None and not self.api_inherit_admin
-
- def get_current_user_roles( self ):
- user = self.user
- if user:
- roles = user.all_roles()
- else:
- roles = []
- return roles
-
- def user_is_admin( self ):
- if self.api_inherit_admin:
- return True
- return self.user and self.user.email in self.app.config.admin_users_list
-
- def user_can_do_run_as( self ):
- run_as_users = [ user for user in self.app.config.get( "api_allow_run_as", "" ).split( "," ) if user ]
- if not run_as_users:
- return False
- user_in_run_as_users = self.user and self.user.email in run_as_users
- # Can do if explicitly in list or master_api_key supplied.
- can_do_run_as = user_in_run_as_users or self.api_inherit_admin
- return can_do_run_as
-
- @property
- def user_ftp_dir( self ):
- identifier = self.app.config.ftp_upload_dir_identifier
- return os.path.join( self.app.config.ftp_upload_dir, getattr(self.user, identifier) )
-
-
-class ProvidesHistoryContext( object ):
- """ For transaction-like objects to provide Galaxy convience layer for
- reasoning about histories.
-
- Mixed in class must provide `user`, `history`, and `app`
- properties.
- """
-
- def db_dataset_for( self, dbkey ):
- """
- Returns the db_file dataset associated/needed by `dataset`, or `None`.
- """
- # If no history, return None.
- if self.history is None:
- return None
- if isinstance(self.history, Bunch):
- # The API presents a Bunch for a history. Until the API is
- # more fully featured for handling this, also return None.
- return None
- datasets = self.sa_session.query( self.app.model.HistoryDatasetAssociation ) \
- .filter_by( deleted=False, history_id=self.history.id, extension="len" )
- for ds in datasets:
- if dbkey == ds.dbkey:
- return ds
- return None
-
- @property
- def db_builds( self ):
- """
- Returns the builds defined by galaxy and the builds defined by
- the user (chromInfo in history).
- """
- # FIXME: This method should be removed
- return self.app.genome_builds.get_genome_build_names( trans=self )
-
-
-class GalaxyWebTransaction( base.DefaultWebTransaction, ProvidesAppContext, ProvidesUserContext, ProvidesHistoryContext ):
- """
- Encapsulates web transaction specific state for the Galaxy application
- (specifically the user's "cookie" session and history)
- """
-
- def __init__( self, environ, app, webapp, session_cookie=None):
- self.app = app
- self.webapp = webapp
- self.security = webapp.security
- base.DefaultWebTransaction.__init__( self, environ )
- self.setup_i18n()
- self.expunge_all()
- self.debug = asbool( self.app.config.get( 'debug', False ) )
- # Flag indicating whether we are in workflow building mode (means
- # that the current history should not be used for parameter values
- # and such).
- self.workflow_building_mode = False
- # Flag indicating whether this is an API call and the API key user is an administrator
- self.api_inherit_admin = False
- self.__user = None
- self.galaxy_session = None
- self.error_message = None
-
- if self.environ.get('is_api_request', False):
- # With API requests, if there's a key, use it and associate the
- # user with the transaction.
- # If not, check for an active session but do not create one.
- # If an error message is set here, it's sent back using
- # trans.show_error in the response -- in expose_api.
- self.error_message = self._authenticate_api( session_cookie )
- elif self.app.name == "reports":
- self.galaxy_session = None
- else:
- # This is a web request, get or create session.
- self._ensure_valid_session( session_cookie )
- if self.galaxy_session:
- # When we've authenticated by session, we have to check the
- # following.
- # Prevent deleted users from accessing Galaxy
- if self.app.config.use_remote_user and self.galaxy_session.user.deleted:
- self.response.send_redirect( url_for( '/static/user_disabled.html' ) )
- if self.app.config.require_login:
- self._ensure_logged_in_user( environ, session_cookie )
-
- def setup_i18n( self ):
- locales = []
- if 'HTTP_ACCEPT_LANGUAGE' in self.environ:
- # locales looks something like: ['en', 'en-us;q=0.7', 'ja;q=0.3']
- client_locales = self.environ['HTTP_ACCEPT_LANGUAGE'].split( ',' )
- for locale in client_locales:
- try:
- locales.append( Locale.parse( locale.split( ';' )[0].strip(), sep='-' ).language )
- except Exception, e:
- log.debug( "Error parsing locale '%s'. %s: %s", locale, type( e ), e )
- if not locales:
- # Default to English
- locales = 'en'
- t = Translations.load( dirname='locale', locales=locales, domain='ginga' )
- self.template_context.update( dict( _=t.ugettext, n_=t.ugettext, N_=t.ungettext ) )
-
- def get_user( self ):
- """Return the current user if logged in or None."""
- if self.galaxy_session:
- return self.galaxy_session.user
- else:
- return self.__user
-
- def set_user( self, user ):
- """Set the current user."""
- if self.galaxy_session:
- self.galaxy_session.user = user
- self.sa_session.add( self.galaxy_session )
- self.sa_session.flush()
- self.__user = user
-
- user = property( get_user, set_user )
-
- def get_cookie( self, name='galaxysession' ):
- """Convenience method for getting a session cookie"""
- try:
- # If we've changed the cookie during the request return the new value
- if name in self.response.cookies:
- return self.response.cookies[name].value
- else:
- return self.request.cookies[name].value
- except:
- return None
-
- def set_cookie( self, value, name='galaxysession', path='/', age=90, version='1' ):
- """Convenience method for setting a session cookie"""
- # The galaxysession cookie value must be a high entropy 128 bit random number encrypted
- # using a server secret key. Any other value is invalid and could pose security issues.
- self.response.cookies[name] = value
- self.response.cookies[name]['path'] = path
- self.response.cookies[name]['max-age'] = 3600 * 24 * age # 90 days
- tstamp = time.localtime( time.time() + 3600 * 24 * age )
- self.response.cookies[name]['expires'] = time.strftime( '%a, %d-%b-%Y %H:%M:%S GMT', tstamp )
- self.response.cookies[name]['version'] = version
- try:
- self.response.cookies[name]['httponly'] = True
- except CookieError, e:
- log.warning( "Error setting httponly attribute in cookie '%s': %s" % ( name, e ) )
-
- def _authenticate_api( self, session_cookie ):
- """
- Authenticate for the API via key or session (if available).
- """
- api_key = self.request.params.get('key', None)
- secure_id = self.get_cookie( name=session_cookie )
- api_key_supplied = self.environ.get('is_api_request', False) and api_key
- if api_key_supplied and self._check_master_api_key( api_key ):
- self.api_inherit_admin = True
- log.info( "Session authenticated using Galaxy master api key" )
- self.user = None
- self.galaxy_session = None
- elif api_key_supplied:
- # Sessionless API transaction, we just need to associate a user.
- try:
- provided_key = self.sa_session.query( self.app.model.APIKeys ).filter( self.app.model.APIKeys.table.c.key == api_key ).one()
- except NoResultFound:
- return 'Provided API key is not valid.'
- if provided_key.user.deleted:
- return 'User account is deactivated, please contact an administrator.'
- newest_key = provided_key.user.api_keys[0]
- if newest_key.key != provided_key.key:
- return 'Provided API key has expired.'
- self.set_user( provided_key.user )
- elif secure_id:
- # API authentication via active session
- # Associate user using existing session
- self._ensure_valid_session( session_cookie )
- else:
- # Anonymous API interaction -- anything but @expose_api_anonymous will fail past here.
- self.user = None
- self.galaxy_session = None
-
- def _check_master_api_key( self, api_key ):
- master_api_key = getattr( self.app.config, 'master_api_key', None )
- if not master_api_key:
- return False
- # Hash keys to make them the same size, so we can do safe comparison.
- master_hash = hashlib.sha256( master_api_key ).hexdigest()
- provided_hash = hashlib.sha256( api_key ).hexdigest()
- return safe_str_cmp( master_hash, provided_hash )
-
- def _ensure_valid_session( self, session_cookie, create=True):
- """
- Ensure that a valid Galaxy session exists and is available as
- trans.session (part of initialization)
-
- Support for universe_session and universe_user cookies has been
- removed as of 31 Oct 2008.
- """
- # Try to load an existing session
- secure_id = self.get_cookie( name=session_cookie )
- galaxy_session = None
- prev_galaxy_session = None
- user_for_new_session = None
- invalidate_existing_session = False
- # Track whether the session has changed so we can avoid calling flush
- # in the most common case (session exists and is valid).
- galaxy_session_requires_flush = False
- if secure_id:
- # Decode the cookie value to get the session_key
- session_key = self.security.decode_guid( secure_id )
- try:
- # Make sure we have a valid UTF-8 string
- session_key = session_key.encode( 'utf8' )
- except UnicodeDecodeError:
- # We'll end up creating a new galaxy_session
- session_key = None
- if session_key:
- # Retrieve the galaxy_session id via the unique session_key
- galaxy_session = self.sa_session.query( self.app.model.GalaxySession ) \
- .filter( and_( self.app.model.GalaxySession.table.c.session_key==session_key, #noqa
- self.app.model.GalaxySession.table.c.is_valid==True ) ).first() #noqa
- # If remote user is in use it can invalidate the session and in some
- # cases won't have a cookie set above, so we need to to check some
- # things now.
- if self.app.config.use_remote_user:
- # If this is an api request, and they've passed a key, we let this go.
- assert self.app.config.remote_user_header in self.environ, \
- "use_remote_user is set but %s header was not provided" % self.app.config.remote_user_header
- remote_user_email = self.environ[ self.app.config.remote_user_header ]
- if getattr( self.app.config, "normalize_remote_user_email", False ):
- remote_user_email = remote_user_email.lower()
- if galaxy_session:
- # An existing session, make sure correct association exists
- if galaxy_session.user is None:
- # No user, associate
- galaxy_session.user = self.get_or_create_remote_user( remote_user_email )
- galaxy_session_requires_flush = True
- elif ((galaxy_session.user.email != remote_user_email) and
- ((not self.app.config.allow_user_impersonation) or
- (remote_user_email not in self.app.config.admin_users_list))):
- # Session exists but is not associated with the correct
- # remote user, and the currently set remote_user is not a
- # potentially impersonating admin.
- invalidate_existing_session = True
- user_for_new_session = self.get_or_create_remote_user( remote_user_email )
- log.warning( "User logged in as '%s' externally, but has a cookie as '%s' invalidating session",
- remote_user_email, galaxy_session.user.email )
- else:
- # No session exists, get/create user for new session
- user_for_new_session = self.get_or_create_remote_user( remote_user_email )
- else:
- if galaxy_session is not None and galaxy_session.user and galaxy_session.user.external:
- # Remote user support is not enabled, but there is an existing
- # session with an external user, invalidate
- invalidate_existing_session = True
- log.warning( "User '%s' is an external user with an existing session, invalidating session since external auth is disabled",
- galaxy_session.user.email )
- elif galaxy_session is not None and galaxy_session.user is not None and galaxy_session.user.deleted:
- invalidate_existing_session = True
- log.warning( "User '%s' is marked deleted, invalidating session" % galaxy_session.user.email )
- # Do we need to invalidate the session for some reason?
- if invalidate_existing_session:
- prev_galaxy_session = galaxy_session
- prev_galaxy_session.is_valid = False
- galaxy_session = None
- # No relevant cookies, or couldn't find, or invalid, so create a new session
- if galaxy_session is None:
- galaxy_session = self.__create_new_session( prev_galaxy_session, user_for_new_session )
- galaxy_session_requires_flush = True
- self.galaxy_session = galaxy_session
- self.__update_session_cookie( name=session_cookie )
- else:
- self.galaxy_session = galaxy_session
- # Do we need to flush the session?
- if galaxy_session_requires_flush:
- self.sa_session.add( galaxy_session )
- # FIXME: If prev_session is a proper relation this would not
- # be needed.
- if prev_galaxy_session:
- self.sa_session.add( prev_galaxy_session )
- self.sa_session.flush()
- # If the old session was invalid, get a new history with our new session
- if invalidate_existing_session:
- self.new_history()
-
- def _ensure_logged_in_user( self, environ, session_cookie ):
- # The value of session_cookie can be one of
- # 'galaxysession' or 'galaxycommunitysession'
- # Currently this method does nothing unless session_cookie is 'galaxysession'
- if session_cookie == 'galaxysession' and self.galaxy_session.user is None:
- # TODO: re-engineer to eliminate the use of allowed_paths
- # as maintenance overhead is far too high.
- allowed_paths = (
- url_for( controller='root', action='index' ),
- url_for( controller='root', action='tool_menu' ),
- url_for( controller='root', action='masthead' ),
- url_for( controller='root', action='history' ),
- url_for( controller='user', action='api_keys' ),
- url_for( controller='user', action='create' ),
- url_for( controller='user', action='index' ),
- url_for( controller='user', action='login' ),
- url_for( controller='user', action='logout' ),
- url_for( controller='user', action='manage_user_info' ),
- url_for( controller='user', action='set_default_permissions' ),
- url_for( controller='user', action='reset_password' ),
- url_for( controller='user', action='openid_auth' ),
- url_for( controller='user', action='openid_process' ),
- url_for( controller='user', action='openid_associate' ),
- url_for( controller='library', action='browse' ),
- url_for( controller='history', action='list' ),
- url_for( controller='dataset', action='list' )
- )
- display_as = url_for( controller='root', action='display_as' )
- if self.app.datatypes_registry.get_display_sites('ucsc') and self.request.path == display_as:
- try:
- host = socket.gethostbyaddr( self.environ[ 'REMOTE_ADDR' ] )[0]
- except( socket.error, socket.herror, socket.gaierror, socket.timeout ):
- host = None
- if host in UCSC_SERVERS:
- return
- external_display_path = url_for( controller='', action='display_application' )
- if self.request.path.startswith( external_display_path ):
- request_path_split = self.request.path.split( '/' )
- try:
- if (self.app.datatypes_registry.display_applications.get( request_path_split[-5] )
- and request_path_split[-4] in self.app.datatypes_registry.display_applications.get( request_path_split[-5] ).links
- and request_path_split[-3] != 'None'):
- return
- except IndexError:
- pass
- if self.request.path not in allowed_paths:
- self.response.send_redirect( url_for( controller='root', action='index' ) )
-
- def __create_new_session( self, prev_galaxy_session=None, user_for_new_session=None ):
- """
- Create a new GalaxySession for this request, possibly with a connection
- to a previous session (in `prev_galaxy_session`) and an existing user
- (in `user_for_new_session`).
-
- Caller is responsible for flushing the returned session.
- """
- session_key = self.security.get_new_guid()
- galaxy_session = self.app.model.GalaxySession(
- session_key=session_key,
- is_valid=True,
- remote_host=self.request.remote_host,
- remote_addr=self.request.remote_addr,
- referer=self.request.headers.get( 'Referer', None ) )
- if prev_galaxy_session:
- # Invalidated an existing session for some reason, keep track
- galaxy_session.prev_session_id = prev_galaxy_session.id
- if user_for_new_session:
- # The new session should be associated with the user
- galaxy_session.user = user_for_new_session
- return galaxy_session
-
- def get_or_create_remote_user( self, remote_user_email ):
- """
- Create a remote user with the email remote_user_email and return it
- """
- if not self.app.config.use_remote_user:
- return None
- if getattr( self.app.config, "normalize_remote_user_email", False ):
- remote_user_email = remote_user_email.lower()
- user = self.sa_session.query( self.app.model.User
- ).filter( self.app.model.User.table.c.email==remote_user_email ).first() #noqa
- if user:
- # GVK: June 29, 2009 - This is to correct the behavior of a previous bug where a private
- # role and default user / history permissions were not set for remote users. When a
- # remote user authenticates, we'll look for this information, and if missing, create it.
- if not self.app.security_agent.get_private_user_role( user ):
- self.app.security_agent.create_private_user_role( user )
- if 'webapp' not in self.environ or self.environ['webapp'] != 'tool_shed':
- if not user.default_permissions:
- self.app.security_agent.user_set_default_permissions( user )
- self.app.security_agent.user_set_default_permissions( user, history=True, dataset=True )
- elif user is None:
- username = remote_user_email.split( '@', 1 )[0].lower()
- random.seed()
- user = self.app.model.User( email=remote_user_email )
- user.set_password_cleartext( ''.join( random.sample( string.letters + string.digits, 12 ) ) )
- user.external = True
- # Replace invalid characters in the username
- for char in filter( lambda x: x not in string.ascii_lowercase + string.digits + '-', username ):
- username = username.replace( char, '-' )
- # Find a unique username - user can change it later
- if ( self.sa_session.query( self.app.model.User ).filter_by( username=username ).first() ):
- i = 1
- while ( self.sa_session.query( self.app.model.User ).filter_by( username=(username + '-' + str(i) ) ).first() ):
- i += 1
- username += '-' + str(i)
- user.username = username
- self.sa_session.add( user )
- self.sa_session.flush()
- self.app.security_agent.create_private_user_role( user )
- # We set default user permissions, before we log in and set the default history permissions
- if 'webapp' not in self.environ or self.environ['webapp'] != 'tool_shed':
- self.app.security_agent.user_set_default_permissions( user )
- # self.log_event( "Automatically created account '%s'", user.email )
- return user
-
- def __update_session_cookie( self, name='galaxysession' ):
- """
- Update the session cookie to match the current session.
- """
- self.set_cookie( self.security.encode_guid(self.galaxy_session.session_key ),
- name=name, path=self.app.config.cookie_path )
-
- def handle_user_login( self, user ):
- """
- Login a new user (possibly newly created)
-
- - create a new session
- - associate new session with user
- - if old session had a history and it was not associated with a user, associate it with the new session,
- otherwise associate the current session's history with the user
- - add the disk usage of the current session to the user's total disk usage
- """
- # Set the previous session
- prev_galaxy_session = self.galaxy_session
- prev_galaxy_session.is_valid = False
- # Define a new current_session
- self.galaxy_session = self.__create_new_session( prev_galaxy_session, user )
- if self.webapp.name == 'galaxy':
- cookie_name = 'galaxysession'
- # Associated the current user's last accessed history (if exists) with their new session
- history = None
- try:
- users_last_session = user.galaxy_sessions[0]
- last_accessed = True
- except:
- users_last_session = None
- last_accessed = False
- if (prev_galaxy_session.current_history and not
- prev_galaxy_session.current_history.deleted and
- prev_galaxy_session.current_history.datasets):
- if prev_galaxy_session.current_history.user is None or prev_galaxy_session.current_history.user == user:
- # If the previous galaxy session had a history, associate it with the new
- # session, but only if it didn't belong to a different user.
- history = prev_galaxy_session.current_history
- if prev_galaxy_session.user is None:
- # Increase the user's disk usage by the amount of the previous history's datasets if they didn't already own it.
- for hda in history.datasets:
- user.total_disk_usage += hda.quota_amount( user )
- elif self.galaxy_session.current_history:
- history = self.galaxy_session.current_history
- if (not history and users_last_session and
- users_last_session.current_history and not
- users_last_session.current_history.deleted):
- history = users_last_session.current_history
- elif not history:
- history = self.get_history( create=True )
- if history not in self.galaxy_session.histories:
- self.galaxy_session.add_history( history )
- if history.user is None:
- history.user = user
- self.galaxy_session.current_history = history
- if not last_accessed:
- # Only set default history permissions if current history is not from a previous session
- self.app.security_agent.history_set_default_permissions( history, dataset=True, bypass_manage_permission=True )
- self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session, history ) )
- else:
- cookie_name = 'galaxycommunitysession'
- self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session ) )
- self.sa_session.flush()
- # This method is not called from the Galaxy reports, so the cookie will always be galaxysession
- self.__update_session_cookie( name=cookie_name )
-
- def handle_user_logout( self, logout_all=False ):
- """
- Logout the current user:
- - invalidate the current session
- - create a new session with no user associated
- """
- prev_galaxy_session = self.galaxy_session
- prev_galaxy_session.is_valid = False
- self.galaxy_session = self.__create_new_session( prev_galaxy_session )
- self.sa_session.add_all( ( prev_galaxy_session, self.galaxy_session ) )
- galaxy_user_id = prev_galaxy_session.user_id
- if logout_all and galaxy_user_id is not None:
- for other_galaxy_session in self.sa_session.query( self.app.model.GalaxySession
- ).filter( and_( self.app.model.GalaxySession.table.c.user_id==galaxy_user_id, #noqa
- self.app.model.GalaxySession.table.c.is_valid==True, #noqa
- self.app.model.GalaxySession.table.c.id!=prev_galaxy_session.id ) ): #noqa
- other_galaxy_session.is_valid = False
- self.sa_session.add( other_galaxy_session )
- self.sa_session.flush()
- if self.webapp.name == 'galaxy':
- # This method is not called from the Galaxy reports, so the cookie will always be galaxysession
- self.__update_session_cookie( name='galaxysession' )
- elif self.webapp.name == 'tool_shed':
- self.__update_session_cookie( name='galaxycommunitysession' )
-
- def get_galaxy_session( self ):
- """
- Return the current galaxy session
- """
- return self.galaxy_session
-
- def get_history( self, create=False ):
- """
- Load the current history, creating a new one only if there is not
- current history and we're told to create.
- Transactions will not always have an active history (API requests), so
- None is a valid response.
- """
- history = None
- if self.galaxy_session:
- history = self.galaxy_session.current_history
- if not history and util.string_as_bool( create ):
- history = self.new_history()
- return history
-
- def set_history( self, history ):
- if history and not history.deleted:
- self.galaxy_session.current_history = history
- self.sa_session.add( self.galaxy_session )
- self.sa_session.flush()
-
- history = property( get_history, set_history )
-
- def get_or_create_default_history( self ):
- """
- Gets or creates a default history and associates it with the current
- session.
- """
-
- # There must be a user to fetch a default history.
- if not self.galaxy_session.user:
- return self.new_history()
-
- # Look for default history that (a) has default name + is not deleted and
- # (b) has no datasets. If suitable history found, use it; otherwise, create
- # new history.
- unnamed_histories = self.sa_session.query( self.app.model.History ).filter_by(
- user=self.galaxy_session.user,
- name=self.app.model.History.default_name,
- deleted=False )
- default_history = None
- for history in unnamed_histories:
- if len( history.datasets ) == 0:
- # Found suitable default history.
- default_history = history
- break
-
- # Set or create hsitory.
- if default_history:
- history = default_history
- self.set_history( history )
- else:
- history = self.new_history()
-
- return history
-
- def new_history( self, name=None ):
- """
- Create a new history and associate it with the current session and
- its associated user (if set).
- """
- # Create new history
- history = self.app.model.History()
- if name:
- history.name = name
- # Associate with session
- history.add_galaxy_session( self.galaxy_session )
- # Make it the session's current history
- self.galaxy_session.current_history = history
- # Associate with user
- if self.galaxy_session.user:
- history.user = self.galaxy_session.user
- # Track genome_build with history
- history.genome_build = self.app.genome_builds.default_value
- # Set the user's default history permissions
- self.app.security_agent.history_set_default_permissions( history )
- # Save
- self.sa_session.add_all( ( self.galaxy_session, history ) )
- self.sa_session.flush()
- return history
-
- @base.lazy_property
- def template_context( self ):
- return dict()
-
- def make_form_data( self, name, **kwargs ):
- rval = self.template_context[name] = FormData()
- rval.values.update( kwargs )
- return rval
-
- def set_message( self, message, type=None ):
- """
- Convenience method for setting the 'message' and 'message_type'
- element of the template context.
- """
- self.template_context['message'] = message
- if type:
- self.template_context['status'] = type
-
- def get_message( self ):
- """
- Convenience method for getting the 'message' element of the template
- context.
- """
- return self.template_context['message']
-
- def show_message( self, message, type='info', refresh_frames=[], cont=None, use_panels=False, active_view="" ):
- """
- Convenience method for displaying a simple page with a single message.
-
- `type`: one of "error", "warning", "info", or "done"; determines the
- type of dialog box and icon displayed with the message
-
- `refresh_frames`: names of frames in the interface that should be
- refreshed when the message is displayed
- """
- return self.fill_template( "message.mako", status=type, message=message, refresh_frames=refresh_frames, cont=cont, use_panels=use_panels, active_view=active_view )
-
- def show_error_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
- """
- Convenience method for displaying an error message. See `show_message`.
- """
- return self.show_message( message, 'error', refresh_frames, use_panels=use_panels, active_view=active_view )
-
- def show_ok_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
- """
- Convenience method for displaying an ok message. See `show_message`.
- """
- return self.show_message( message, 'done', refresh_frames, use_panels=use_panels, active_view=active_view )
-
- def show_warn_message( self, message, refresh_frames=[], use_panels=False, active_view="" ):
- """
- Convenience method for displaying an warn message. See `show_message`.
- """
- return self.show_message( message, 'warning', refresh_frames, use_panels=use_panels, active_view=active_view )
-
- def show_form( self, form, header=None, template="form.mako", use_panels=False, active_view="" ):
- """
- Convenience method for displaying a simple page with a single HTML
- form.
- """
- return self.fill_template( template, form=form, header=header,
- use_panels=( form.use_panels or use_panels ),
- active_view=active_view )
-
- def fill_template(self, filename, **kwargs):
- """
- Fill in a template, putting any keyword arguments on the context.
- """
- # call get_user so we can invalidate sessions from external users,
- # if external auth has been disabled.
- self.get_user()
- if filename.endswith( ".mako" ):
- return self.fill_template_mako( filename, **kwargs )
- else:
- template = Template( file=os.path.join(self.app.config.template_path, filename),
- searchList=[kwargs, self.template_context, dict(caller=self, t=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app)] )
- return str( template )
-
- def fill_template_mako( self, filename, template_lookup=None, **kwargs ):
- template_lookup = template_lookup or self.webapp.mako_template_lookup
- template = template_lookup.get_template( filename )
- template.output_encoding = 'utf-8'
-
- data = dict( caller=self, t=self, trans=self, h=helpers, util=util,
- request=self.request, response=self.response, app=self.app )
- data.update( self.template_context )
- data.update( kwargs )
- return template.render( **data )
-
- def stream_template_mako( self, filename, **kwargs ):
- template = self.webapp.mako_template_lookup.get_template( filename )
- template.output_encoding = 'utf-8'
- data = dict( caller=self, t=self, trans=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app )
- data.update( self.template_context )
- data.update( kwargs )
-
- def render( environ, start_response ):
- response_write = start_response( self.response.wsgi_status(), self.response.wsgi_headeritems() )
-
- class StreamBuffer( object ):
- def write( self, d ):
- response_write( d.encode( 'utf-8' ) )
- buffer = StreamBuffer()
- context = mako.runtime.Context( buffer, **data )
- template.render_context( context )
- return []
- return render
-
- def fill_template_string(self, template_string, context=None, **kwargs):
- """
- Fill in a template, putting any keyword arguments on the context.
- """
- template = Template( source=template_string,
- searchList=[context or kwargs, dict(caller=self)] )
- return str(template)
-
-
-class FormBuilder( object ):
- """
- Simple class describing an HTML form
- """
- def __init__( self, action="", title="", name="form", submit_text="submit", use_panels=False ):
- self.title = title
- self.name = name
- self.action = action
- self.submit_text = submit_text
- self.inputs = []
- self.use_panels = use_panels
-
- def add_input( self, type, name, label, value=None, error=None, help=None, use_label=True ):
- self.inputs.append( FormInput( type, label, name, value, error, help, use_label ) )
- return self
-
- def add_text( self, name, label, value=None, error=None, help=None ):
- return self.add_input( 'text', label, name, value, error, help )
-
- def add_password( self, name, label, value=None, error=None, help=None ):
- return self.add_input( 'password', label, name, value, error, help )
-
- def add_select( self, name, label, value=None, options=[], error=None, help=None, use_label=True ):
- self.inputs.append( SelectInput( name, label, value=value, options=options, error=error, help=help, use_label=use_label ) )
- return self
-
-
-class FormInput( object ):
- """
- Simple class describing a form input element
- """
- def __init__( self, type, name, label, value=None, error=None, help=None, use_label=True, extra_attributes={}, **kwargs ):
- self.type = type
- self.name = name
- self.label = label
- self.value = value
- self.error = error
- self.help = help
- self.use_label = use_label
- self.extra_attributes = extra_attributes
-
-
-class DatalistInput( FormInput ):
- """ Data list input """
-
- def __init__( self, name, *args, **kwargs ):
- if 'extra_attributes' not in kwargs:
- kwargs[ 'extra_attributes' ] = {}
- kwargs[ 'extra_attributes' ][ 'list' ] = name
- FormInput.__init__( self, None, name, *args, **kwargs )
- self.options = kwargs.get( 'options', {} )
-
- def body_html( self ):
- options = "".join( [ "<option value='%s'>%s</option>" % ( key, value ) for key, value in self.options.iteritems() ] )
- return """<datalist id="%s">%s</datalist>""" % ( self.name, options )
-
-
-class SelectInput( FormInput ):
- """ A select form input. """
- def __init__( self, name, label, value=None, options=[], error=None, help=None, use_label=True ):
- FormInput.__init__( self, "select", name, label, value=value, error=error, help=help, use_label=use_label )
- self.options = options
-
-
-class FormData( object ):
- """
- Class for passing data about a form to a template, very rudimentary, could
- be combined with the tool form handling to build something more general.
- """
- def __init__( self ):
- self.values = Bunch()
- self.errors = Bunch()
-
-
-class Bunch( dict ):
- """
- Bunch based on a dict
- """
- def __getattr__( self, key ):
- if key not in self:
- raise AttributeError(key)
- return self[key]
-
- def __setattr__( self, key, value ):
- self[key] = value
This diff is so big that we needed to truncate the remainder.
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: dannon: Merged in lance_parsons/galaxy-central-pull-requests/venv (pull request #486)
by commits-noreply@bitbucket.org 04 Sep '14
by commits-noreply@bitbucket.org 04 Sep '14
04 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/1eedbc8a3a8d/
Changeset: 1eedbc8a3a8d
User: dannon
Date: 2014-09-04 22:25:37
Summary: Merged in lance_parsons/galaxy-central-pull-requests/venv (pull request #486)
Source virtualenv prior to python check and source venv for reports webapp
Affected #: 2 files
diff -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f run.sh
--- a/run.sh
+++ b/run.sh
@@ -2,6 +2,13 @@
cd `dirname $0`
+# If there is a .venv/ directory, assume it contains a virtualenv that we
+# should run this instance in.
+if [ -d .venv ];
+then
+ . .venv/bin/activate
+fi
+
python ./scripts/check_python.py
[ $? -ne 0 ] && exit 1
@@ -11,13 +18,6 @@
python ./scripts/build_universe_config.py "$GALAXY_UNIVERSE_CONFIG_DIR"
fi
-# If there is a .venv/ directory, assume it contains a virtualenv that we
-# should run this instance in.
-if [ -d .venv ];
-then
- . .venv/bin/activate
-fi
-
if [ -n "$GALAXY_RUN_ALL" ]; then
servers=`sed -n 's/^\[server:\(.*\)\]/\1/ p' universe_wsgi.ini | xargs echo`
daemon=`echo "$@" | grep -q daemon`
diff -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f run_reports.sh
--- a/run_reports.sh
+++ b/run_reports.sh
@@ -11,6 +11,13 @@
cd `dirname $0`
+# If there is a .venv/ directory, assume it contains a virtualenv that we
+# should run this instance in.
+if [ -d .venv ];
+then
+ . .venv/bin/activate
+fi
+
./scripts/common_startup.sh --skip-samples
GALAXY_REPORTS_CONFIG=${GALAXY_REPORTS_CONFIG:-reports_wsgi.ini}
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/22c4bcbfc20f/
Changeset: 22c4bcbfc20f
Branch: venv
User: lance_parsons
Date: 2014-09-04 21:04:34
Summary: Created new branch venv
Affected #: 0 files
https://bitbucket.org/galaxy/galaxy-central/commits/619a8d213032/
Changeset: 619a8d213032
Branch: venv
User: lance_parsons
Date: 2014-09-04 21:12:04
Summary: Source virtualenv before python check and for reports app
Affected #: 2 files
diff -r 22c4bcbfc20f116d45398f93b88666ad8a94a4e7 -r 619a8d213032b726eded009572c2ebfc78c70834 run.sh
--- a/run.sh
+++ b/run.sh
@@ -2,6 +2,13 @@
cd `dirname $0`
+# If there is a .venv/ directory, assume it contains a virtualenv that we
+# should run this instance in.
+if [ -d .venv ];
+then
+ . .venv/bin/activate
+fi
+
python ./scripts/check_python.py
[ $? -ne 0 ] && exit 1
@@ -11,13 +18,6 @@
python ./scripts/build_universe_config.py "$GALAXY_UNIVERSE_CONFIG_DIR"
fi
-# If there is a .venv/ directory, assume it contains a virtualenv that we
-# should run this instance in.
-if [ -d .venv ];
-then
- . .venv/bin/activate
-fi
-
if [ -n "$GALAXY_RUN_ALL" ]; then
servers=`sed -n 's/^\[server:\(.*\)\]/\1/ p' universe_wsgi.ini | xargs echo`
daemon=`echo "$@" | grep -q daemon`
diff -r 22c4bcbfc20f116d45398f93b88666ad8a94a4e7 -r 619a8d213032b726eded009572c2ebfc78c70834 run_reports.sh
--- a/run_reports.sh
+++ b/run_reports.sh
@@ -11,6 +11,13 @@
cd `dirname $0`
+# If there is a .venv/ directory, assume it contains a virtualenv that we
+# should run this instance in.
+if [ -d .venv ];
+then
+ . .venv/bin/activate
+fi
+
./scripts/common_startup.sh --skip-samples
GALAXY_REPORTS_CONFIG=${GALAXY_REPORTS_CONFIG:-reports_wsgi.ini}
https://bitbucket.org/galaxy/galaxy-central/commits/1eedbc8a3a8d/
Changeset: 1eedbc8a3a8d
User: dannon
Date: 2014-09-04 22:25:37
Summary: Merged in lance_parsons/galaxy-central-pull-requests/venv (pull request #486)
Source virtualenv prior to python check and source venv for reports webapp
Affected #: 2 files
diff -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f run.sh
--- a/run.sh
+++ b/run.sh
@@ -2,6 +2,13 @@
cd `dirname $0`
+# If there is a .venv/ directory, assume it contains a virtualenv that we
+# should run this instance in.
+if [ -d .venv ];
+then
+ . .venv/bin/activate
+fi
+
python ./scripts/check_python.py
[ $? -ne 0 ] && exit 1
@@ -11,13 +18,6 @@
python ./scripts/build_universe_config.py "$GALAXY_UNIVERSE_CONFIG_DIR"
fi
-# If there is a .venv/ directory, assume it contains a virtualenv that we
-# should run this instance in.
-if [ -d .venv ];
-then
- . .venv/bin/activate
-fi
-
if [ -n "$GALAXY_RUN_ALL" ]; then
servers=`sed -n 's/^\[server:\(.*\)\]/\1/ p' universe_wsgi.ini | xargs echo`
daemon=`echo "$@" | grep -q daemon`
diff -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 -r 1eedbc8a3a8d6abcb8da2a7db246cb4f924b763f run_reports.sh
--- a/run_reports.sh
+++ b/run_reports.sh
@@ -11,6 +11,13 @@
cd `dirname $0`
+# If there is a .venv/ directory, assume it contains a virtualenv that we
+# should run this instance in.
+if [ -d .venv ];
+then
+ . .venv/bin/activate
+fi
+
./scripts/common_startup.sh --skip-samples
GALAXY_REPORTS_CONFIG=${GALAXY_REPORTS_CONFIG:-reports_wsgi.ini}
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: natefoo: Merged in natefoo/galaxy-central (pull request #481)
by commits-noreply@bitbucket.org 04 Sep '14
by commits-noreply@bitbucket.org 04 Sep '14
04 Sep '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9b6cccb3af2d/
Changeset: 9b6cccb3af2d
User: natefoo
Date: 2014-09-04 17:38:26
Summary: Merged in natefoo/galaxy-central (pull request #481)
Make build sites files configurable
Affected #: 27 files
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 datatypes_conf.xml.sample
--- a/datatypes_conf.xml.sample
+++ b/datatypes_conf.xml.sample
@@ -312,4 +312,22 @@
<sniffer type="galaxy.datatypes.assembly:Amos"/>
--></sniffers>
+ <build_sites>
+ <!--
+ Build sites define the builds (dbkeys) available at sites used by display
+ applications and the URL to those sites.
+
+ The `display` attributes on the `ucsc` and `gbrowse` sites replace the
+ `ucsc_display_sites` and `gbrowse_display_sites` options in
+ universe_wsgi.ini. Because these are used by "old-style" display
+ applications, their types cannot change if you want the old-style display
+ links for these sites to work.
+ -->
+ <site type="ucsc" file="tool-data/shared/ucsc/ucsc_build_sites.txt" display="main,test,archaea,ucla"/>
+ <site type="gbrowse" file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" display="modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225"/>
+ <site type="ensembl" file="tool-data/shared/ensembl/ensembl_sites.txt"/>
+ <site type="ensembl_data_url" file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt"/>
+ <site type="igv" file="tool-data/shared/igv/igv_build_sites.txt"/>
+ <site type="rviewer" file="tool-data/shared/rviewer/rviewer_build_sites.txt"/>
+ </build_sites></datatypes>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ensembl/ensembl_bam.xml
--- a/display_applications/ensembl/ensembl_bam.xml
+++ b/display_applications/ensembl/ensembl_bam.xml
@@ -1,7 +1,7 @@
<display id="ensembl_bam" version="1.0.0" name="display at Ensembl"><!-- Current Ensembl method of attaching user data via URL; archives older than ~November 2008 will use a different method --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ensembl/ensembl_gff.xml
--- a/display_applications/ensembl/ensembl_gff.xml
+++ b/display_applications/ensembl/ensembl_gff.xml
@@ -1,7 +1,7 @@
<display id="ensembl_gff" version="1.0.0" name="display at Ensembl"><!-- Current Ensembl method of attaching user data via URL; archives older than ~November 2008 will use a different method --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -41,7 +41,7 @@
<!-- Old Ensembl method of attaching user data via URL --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl_data_url" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ensembl/ensembl_interval_as_bed.xml
--- a/display_applications/ensembl/ensembl_interval_as_bed.xml
+++ b/display_applications/ensembl/ensembl_interval_as_bed.xml
@@ -1,7 +1,7 @@
<display id="ensembl_interval" version="1.0.0" name="display at Ensembl"><!-- Current Ensembl method of attaching user data via URL; archives older than ~November 2008 will use a different method --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -41,7 +41,7 @@
<!-- Old Ensembl method of attaching user data via URL --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl_data_url" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/gbrowse/gbrowse_gff.xml
--- a/display_applications/gbrowse/gbrowse_gff.xml
+++ b/display_applications/gbrowse/gbrowse_gff.xml
@@ -1,6 +1,6 @@
<display id="gbrowse_gff" version="1.0.0" name="display at GBrowse"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="gbrowse" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -10,7 +10,7 @@
<dynamic_param name="site_organisms" value="4" split="True" separator="," /><!-- Filter out some of the links based upon matching site_id to a Galaxy application configuration parameter and by dataset dbkey -->
- <filter>${site_id in $APP.config.gbrowse_display_sites}</filter>
+ <filter>${site_id in $APP.datatypes_registry.get_display_sites('gbrowse')}</filter><filter>${dataset.dbkey in $site_dbkeys}</filter><!-- We define url and params as normal, but values defined in dynamic_param are available by specified name -->
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/gbrowse/gbrowse_interval_as_bed.xml
--- a/display_applications/gbrowse/gbrowse_interval_as_bed.xml
+++ b/display_applications/gbrowse/gbrowse_interval_as_bed.xml
@@ -1,6 +1,6 @@
<display id="gbrowse_interval_as_bed" version="1.0.0" name="display at GBrowse"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="gbrowse" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -10,7 +10,7 @@
<dynamic_param name="site_organisms" value="4" split="True" separator="," /><!-- Filter out some of the links based upon matching site_id to a Galaxy application configuration parameter and by dataset dbkey -->
- <filter>${site_id in $APP.config.gbrowse_display_sites}</filter>
+ <filter>${site_id in $APP.datatypes_registry.get_display_sites('gbrowse')}</filter><filter>${dataset.dbkey in $site_dbkeys}</filter><!-- We define url and params as normal, but values defined in dynamic_param are available by specified name -->
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/gbrowse/gbrowse_wig.xml
--- a/display_applications/gbrowse/gbrowse_wig.xml
+++ b/display_applications/gbrowse/gbrowse_wig.xml
@@ -1,6 +1,6 @@
<display id="gbrowse_wig" version="1.0.0" name="display at GBrowse"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="gbrowse" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -10,7 +10,7 @@
<dynamic_param name="site_organisms" value="4" split="True" separator="," /><!-- Filter out some of the links based upon matching site_id to a Galaxy application configuration parameter and by dataset dbkey -->
- <filter>${site_id in $APP.config.gbrowse_display_sites}</filter>
+ <filter>${site_id in $APP.datatypes_registry.get_display_sites('gbrowse')}</filter><filter>${dataset.dbkey in $site_dbkeys}</filter><!-- We define url and params as normal, but values defined in dynamic_param are available by specified name -->
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/igv/bam.xml
--- a/display_applications/igv/bam.xml
+++ b/display_applications/igv/bam.xml
@@ -2,7 +2,7 @@
<display id="igv_bam" version="1.0.0" name="display with IGV"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/igv/igv_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="igv" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -92,4 +92,4 @@
</display>
-<!-- Dan Blankenberg -->
\ No newline at end of file
+<!-- Dan Blankenberg -->
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/igv/vcf.xml
--- a/display_applications/igv/vcf.xml
+++ b/display_applications/igv/vcf.xml
@@ -2,7 +2,7 @@
<display id="igv_vcf" version="1.0.0" name="display with IGV"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/igv/igv_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="igv" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -92,4 +92,4 @@
</display>
-<!-- Dan Blankenberg -->
\ No newline at end of file
+<!-- Dan Blankenberg -->
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/rviewer/bed.xml
--- a/display_applications/rviewer/bed.xml
+++ b/display_applications/rviewer/bed.xml
@@ -2,7 +2,7 @@
<display id="rviewer_interval" version="1.0.0" name="display at RViewer"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/rviewer/rviewer_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="rviewer" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/rviewer/vcf.xml
--- a/display_applications/rviewer/vcf.xml
+++ b/display_applications/rviewer/vcf.xml
@@ -2,7 +2,7 @@
<display id="rviewer_vcf" version="1.0.0" name="display at RViewer"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/rviewer/rviewer_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="rviewer" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ucsc/bam.xml
--- a/display_applications/ucsc/bam.xml
+++ b/display_applications/ucsc/bam.xml
@@ -1,6 +1,6 @@
<display id="ucsc_bam" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ucsc/bigbed.xml
--- a/display_applications/ucsc/bigbed.xml
+++ b/display_applications/ucsc/bigbed.xml
@@ -1,6 +1,6 @@
<display id="ucsc_bigbed" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ucsc/bigwig.xml
--- a/display_applications/ucsc/bigwig.xml
+++ b/display_applications/ucsc/bigwig.xml
@@ -1,6 +1,6 @@
<display id="ucsc_bigwig" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ucsc/interval_as_bed.xml
--- a/display_applications/ucsc/interval_as_bed.xml
+++ b/display_applications/ucsc/interval_as_bed.xml
@@ -1,6 +1,6 @@
<display id="ucsc_interval_as_bed" version="1.0.0" name="display at UCSC" inherit="True"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ucsc/maf_customtrack.xml
--- a/display_applications/ucsc/maf_customtrack.xml
+++ b/display_applications/ucsc/maf_customtrack.xml
@@ -1,6 +1,6 @@
<display id="ucsc_maf_customtrack" version="1.0.0" name="display at UCSC" inherit="True"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ucsc/vcf.xml
--- a/display_applications/ucsc/vcf.xml
+++ b/display_applications/ucsc/vcf.xml
@@ -1,6 +1,6 @@
<display id="ucsc_vcf" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/app.py
--- a/lib/galaxy/app.py
+++ b/lib/galaxy/app.py
@@ -64,9 +64,6 @@
# Load dbkey / genome build manager
self._configure_genome_builds( data_table_name="__dbkeys__", load_old_style=True )
- # Load build sites (old-style)
- self._configure_build_sites()
-
# Genomes
self.genomes = Genomes( self )
# Data providers registry.
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -16,7 +16,6 @@
from galaxy.util import string_as_bool
from galaxy.util import listify
from galaxy.util.dbkeys import GenomeBuilds
-from galaxy.util.build_sites import BuildSites
from galaxy import eggs
log = logging.getLogger( __name__ )
@@ -224,10 +223,6 @@
self.sanitize_all_html = string_as_bool( kwargs.get( 'sanitize_all_html', True ) )
self.serve_xss_vulnerable_mimetypes = string_as_bool( kwargs.get( 'serve_xss_vulnerable_mimetypes', False ) )
self.enable_old_display_applications = string_as_bool( kwargs.get( "enable_old_display_applications", "True" ) )
- self.ucsc_build_sites = resolve_path( kwargs.get( 'ucsc_build_sites', os.path.join( self.tool_data_path, 'shared', 'ucsc', 'ucsc_build_sites.txt') ), self.root )
- self.ucsc_display_sites = kwargs.get( 'ucsc_display_sites', "main,test,archaea,ucla" ).lower().split(",")
- self.gbrowse_build_sites = resolve_path( kwargs.get( 'gbrowse_build_sites', os.path.join( self.tool_data_path, 'shared', 'gbrowse', 'gbrowse_build_sites.txt') ), self.root )
- self.gbrowse_display_sites = kwargs.get( 'gbrowse_display_sites', "modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225" ).lower().split(",")
self.brand = kwargs.get( 'brand', None )
self.welcome_url = kwargs.get( 'welcome_url', '/static/welcome.html' )
# Configuration for the message box directly below the masthead.
@@ -575,9 +570,6 @@
def _configure_genome_builds( self, data_table_name="__dbkeys__", load_old_style=True ):
self.genome_builds = GenomeBuilds( self, data_table_name=data_table_name, load_old_style=load_old_style )
- def _configure_build_sites( self ):
- self.build_sites = BuildSites( self )
-
def _configure_toolbox( self ):
# Initialize the tools, making sure the list of tool configs includes the reserved migrated_tools_conf.xml file.
tool_configs = self.config.tool_configs
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/datatypes/display_applications/application.py
--- a/lib/galaxy/datatypes/display_applications/application.py
+++ b/lib/galaxy/datatypes/display_applications/application.py
@@ -81,9 +81,13 @@
class DynamicDisplayApplicationBuilder( object ):
@classmethod
- def __init__( self, elem, display_application ):
+ def __init__( self, elem, display_application, build_sites ):
rval = []
- filename = elem.get( 'from_file', None )
+ filename = None
+ if elem.get( 'site_type', None ) is not None:
+ filename = build_sites.get( elem.get( 'site_type' ) )
+ else:
+ filename = elem.get( 'from_file', None )
assert filename is not None, 'Filename and id attributes required for dynamic_links'
skip_startswith = elem.get( 'skip_startswith', None )
separator = elem.get( 'separator', '\t' )
@@ -176,7 +180,7 @@
if link:
rval.links[ link.id ] = link
for dynamic_links in elem.findall( 'dynamic_links' ):
- for link in DynamicDisplayApplicationBuilder( dynamic_links, rval ):
+ for link in DynamicDisplayApplicationBuilder( dynamic_links, rval, datatypes_registry.build_sites ):
rval.links[ link.id ] = link
return rval
def __init__( self, display_id, name, datatypes_registry, version = None ):
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/datatypes/genetics.py
--- a/lib/galaxy/datatypes/genetics.py
+++ b/lib/galaxy/datatypes/genetics.py
@@ -83,29 +83,31 @@
ret_val = []
ggtail = 'hgGenome_doSubmitUpload=submit'
if not dataset.dbkey:
- dataset.dbkey = 'hg18' # punt!
+ dataset.dbkey = 'hg18' # punt!
if dataset.has_data():
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build(dataset.dbkey):
- if site_name in app.config.ucsc_display_sites:
- site_url = site_url.replace('/hgTracks?','/hgGenome?') # for genome graphs
- internal_url = "%s" % url_for( controller='dataset',
- dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name )
- display_url = "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type)
- display_url = urllib.quote_plus( display_url )
- # was display_url = urllib.quote_plus( "%s/display_as?id=%i&display_app=%s" % (base_url, dataset.id, type) )
- #redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop) )
- sl = ["%sdb=%s" % (site_url,dataset.dbkey ),]
- #sl.append("&hgt.customText=%s")
- sl.append("&hgGenome_dataSetName=%s&hgGenome_dataSetDescription=%s" % (dataset.name, 'GalaxyGG_data'))
- sl.append("&hgGenome_formatType=best guess&hgGenome_markerType=best guess")
- sl.append("&hgGenome_columnLabels=first row&hgGenome_maxVal=&hgGenome_labelVals=")
- sl.append("&hgGenome_doSubmitUpload=submit")
- sl.append("&hgGenome_maxGapToFill=25000000&hgGenome_uploadFile=%s" % display_url)
- s = ''.join(sl)
- s = urllib.quote_plus(s)
- redirect_url = s
- link = '%s?redirect_url=%s&display_url=%s' % ( internal_url, redirect_url, display_url )
- ret_val.append( (site_name, link) )
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey):
+ if site_name in datatypes_registry.get_display_sites('ucsc'):
+ site_url = site_url.replace('/hgTracks?','/hgGenome?') # for genome graphs
+ internal_url = "%s" % url_for( controller='dataset',
+ dataset_id=dataset.id,
+ action='display_at',
+ filename='ucsc_' + site_name )
+ display_url = "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type)
+ display_url = urllib.quote_plus( display_url )
+ # was display_url = urllib.quote_plus( "%s/display_as?id=%i&display_app=%s" % (base_url, dataset.id, type) )
+ #redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop) )
+ sl = ["%sdb=%s" % (site_url,dataset.dbkey ),]
+ #sl.append("&hgt.customText=%s")
+ sl.append("&hgGenome_dataSetName=%s&hgGenome_dataSetDescription=%s" % (dataset.name, 'GalaxyGG_data'))
+ sl.append("&hgGenome_formatType=best guess&hgGenome_markerType=best guess")
+ sl.append("&hgGenome_columnLabels=first row&hgGenome_maxVal=&hgGenome_labelVals=")
+ sl.append("&hgGenome_doSubmitUpload=submit")
+ sl.append("&hgGenome_maxGapToFill=25000000&hgGenome_uploadFile=%s" % display_url)
+ s = ''.join(sl)
+ s = urllib.quote_plus(s)
+ redirect_url = s
+ link = '%s?redirect_url=%s&display_url=%s' % ( internal_url, redirect_url, display_url )
+ ret_val.append( (site_name, link) )
return ret_val
def make_html_table( self, dataset, skipchars=[] ):
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/datatypes/interval.py
--- a/lib/galaxy/datatypes/interval.py
+++ b/lib/galaxy/datatypes/interval.py
@@ -234,8 +234,8 @@
# Filter UCSC sites to only those that are supported by this build and
# enabled.
valid_sites = [ ( name, url )
- for name, url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey )
- if name in app.config.ucsc_display_sites ]
+ for name, url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey )
+ if name in app.datatypes_registry.get_display_sites('ucsc') ]
if not valid_sites:
return []
# If there are any valid sites, we need to generate the estimated
@@ -750,8 +750,8 @@
ret_val = []
seqid, start, stop = self.get_estimated_display_viewport( dataset )
if seqid is not None:
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey ):
- if site_name in app.config.ucsc_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey ):
+ if site_name in app.datatypes_registry.get_display_sites('ucsc'):
redirect_url = urllib.quote_plus(
"%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" %
( site_url, dataset.dbkey, seqid, start, stop ) )
@@ -762,8 +762,8 @@
ret_val = []
seqid, start, stop = self.get_estimated_display_viewport( dataset )
if seqid is not None:
- for site_name, site_url in app.build_sites.get_gbrowse_sites_by_build( dataset.dbkey ):
- if site_name in app.config.gbrowse_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('gbrowse', dataset.dbkey ):
+ if site_name in app.datatypes_registry.get_display_sites('gbrowse'):
if seqid.startswith( 'chr' ) and len ( seqid ) > 3:
seqid = seqid[3:]
redirect_url = urllib.quote_plus( "%s/?q=%s:%s..%s&eurl=%%s" % ( site_url, seqid, start, stop ) )
@@ -1091,8 +1091,8 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport( dataset )
if chrom is not None:
- for site_name, site_url in app.build_sites.get_gbrowse_sites_by_build( dataset.dbkey ):
- if site_name in app.config.gbrowse_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('gbrowse', dataset.dbkey ):
+ if site_name in app.datatypes_registry.get_display_sites('gbrowse'):
if chrom.startswith( 'chr' ) and len ( chrom ) > 3:
chrom = chrom[3:]
redirect_url = urllib.quote_plus( "%s/?q=%s:%s..%s&eurl=%%s" % ( site_url, chrom, start, stop ) )
@@ -1103,8 +1103,8 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport( dataset )
if chrom is not None:
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey ):
- if site_name in app.config.ucsc_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey ):
+ if site_name in app.datatypes_registry.get_display_sites('ucsc'):
redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % ( site_url, dataset.dbkey, chrom, start, stop ) )
link = self._get_remote_call_url( redirect_url, site_name, dataset, type, app, base_url )
ret_val.append( ( site_name, link ) )
@@ -1285,8 +1285,8 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport(dataset)
if chrom is not None:
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build(dataset.dbkey):
- if site_name in app.config.ucsc_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc',dataset.dbkey):
+ if site_name in app.datatypes_registry.get_display_sites('ucsc'):
internal_url = "%s" % url_for( controller='dataset', dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name )
display_url = urllib.quote_plus( "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type) )
redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop ) )
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -69,6 +69,10 @@
self.datatype_elems = []
self.sniffer_elems = []
self.xml_filename = None
+ # Build sites
+ self.build_sites = {}
+ self.display_sites = {}
+ self.legacy_build_sites = {}
def load_datatypes( self, root_dir=None, config=None, deactivate=False, override=True ):
"""
@@ -285,6 +289,8 @@
handling_proprietary_datatypes=handling_proprietary_datatypes,
override=override )
self.upload_file_formats.sort()
+ # Load build sites
+ self.load_build_sites( root )
# Persist the xml form of the registry into a temporary file so that it can be loaded from the command line by tools and
# set_metadata processing.
self.to_xml_file()
@@ -303,6 +309,34 @@
self.sniff_order.append( datatype )
append_to_sniff_order()
+ def load_build_sites( self, root ):
+ if root.find( 'build_sites' ):
+ for elem in root.find( 'build_sites' ).findall( 'site' ):
+ if not (elem.get( 'type' ) and elem.get( 'file' )):
+ self.log.exception( "Site is missing required 'type' and 'file' attributes: %s" )
+ else:
+ site_type = elem.get( 'type' )
+ file = elem.get( 'file' )
+ self.build_sites[site_type] = file
+ if site_type in ('ucsc', 'gbrowse'):
+ self.legacy_build_sites[site_type] = galaxy.util.read_build_sites( file )
+ if elem.get( 'display', None ):
+ display = elem.get( 'display' )
+ self.display_sites[site_type] = [ x.strip() for x in display.lower().split( ',' ) ]
+ self.log.debug( "Loaded build site '%s': %s with display sites: %s", site_type, file, display )
+ else:
+ self.log.debug( "Loaded build site '%s': %s", site_type, file )
+
+ def get_legacy_sites_by_build( self, site_type, build ):
+ sites = []
+ for site in self.legacy_build_sites.get(site_type, []):
+ if build in site['builds']:
+ sites.append((site['name'], site['url']))
+ return sites
+
+ def get_display_sites( self, site_type ):
+ return self.display_sites.get( site_type, [] )
+
def load_datatype_sniffers( self, root, deactivate=False, handling_proprietary_datatypes=False, override=False ):
"""
Process the sniffers element from a parsed a datatypes XML file located at root_dir/config (if processing the Galaxy
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/util/__init__.py
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -844,6 +844,30 @@
return db_names
+def read_build_sites( filename, check_builds=True ):
+ """ read db names to ucsc mappings from file, this file should probably be merged with the one above """
+ build_sites = []
+ try:
+ for line in open(filename):
+ try:
+ if line[0:1] == "#":
+ continue
+ fields = line.replace("\r", "").replace("\n", "").split("\t")
+ site_name = fields[0]
+ site = fields[1]
+ if check_builds:
+ site_builds = fields[2].split(",")
+ site_dict = {'name': site_name, 'url': site, 'builds': site_builds}
+ else:
+ site_dict = {'name': site_name, 'url': site}
+ build_sites.append( site_dict )
+ except:
+ continue
+ except:
+ print "ERROR: Unable to read builds for site file %s" % filename
+ return build_sites
+
+
def relativize_symlinks( path, start=None, followlinks=False):
for root, dirs, files in os.walk( path, followlinks=followlinks ):
rel_start = None
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/util/build_sites.py
--- a/lib/galaxy/util/build_sites.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
-Functionality for dealing with build sites for legacy display applications.
-"""
-import os.path
-
-
-class BuildSites( object ):
-
- def __init__( self, app ):
- self._app = app
- self._build_sites = {}
- self.load_build_sites()
-
- def read_build_sites( self, filename, check_builds=True ):
- """ read db names to ucsc mappings from file, this file should probably be merged with the one above """
- build_sites = []
- try:
- for line in open(filename):
- try:
- if line[0:1] == "#":
- continue
- fields = line.replace("\r", "").replace("\n", "").split("\t")
- site_name = fields[0]
- site = fields[1]
- if check_builds:
- site_builds = fields[2].split(",")
- site_dict = {'name': site_name, 'url': site, 'builds': site_builds}
- else:
- site_dict = {'name': site_name, 'url': site}
- build_sites.append( site_dict )
- except:
- continue
- except:
- print "ERROR: Unable to read builds for site file %s" % filename
- return build_sites
-
- def load_build_sites( self ):
- self._build_sites['ucsc'] = self.read_build_sites( self._app.config.ucsc_build_sites )
- self._build_sites['gbrowse'] = self.read_build_sites( self._app.config.gbrowse_build_sites )
-
- def _get_site_by_build( self, site_type, build ):
- sites = []
- for site in self._build_sites[site_type]:
- if build in site['builds']:
- sites.append((site['name'], site['url']))
- return sites
-
- def get_ucsc_sites_by_build( self, build ):
- return self._get_site_by_build( 'ucsc', build )
-
- def get_gbrowse_sites_by_build( self, build ):
- return self._get_site_by_build( 'gbrowse', build )
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -926,7 +926,7 @@
url_for( controller='dataset', action='list' )
)
display_as = url_for( controller='root', action='display_as' )
- if self.app.config.ucsc_display_sites and self.request.path == display_as:
+ if self.app.datatypes_registry.get_display_sites('ucsc') and self.request.path == display_as:
try:
host = socket.gethostbyaddr( self.environ[ 'REMOTE_ADDR' ] )[0]
except( socket.error, socket.herror, socket.gaierror, socket.timeout ):
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 universe_wsgi.ini.sample
--- a/universe_wsgi.ini.sample
+++ b/universe_wsgi.ini.sample
@@ -364,14 +364,6 @@
# which browsers should be available. URLs and builds available at these
# browsers are defined in the specifield files.
-# UCSC browsers:
-#ucsc_build_sites = tool-data/shared/ucsc/ucsc_build_sites.txt
-#ucsc_display_sites = main,test,archaea,ucla
-
-# GBrowse servers:
-#gbrowse_build_sites = tool-data/shared/gbrowse/gbrowse_build_sites.txt
-#gbrowse_display_sites = modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225
-
# If use_remote_user = True, display application servers will be denied access
# to Galaxy and so displaying datasets in these sites will fail.
# display_servers contains a list of hostnames which should be allowed to
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
6 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/a3c5b729592b/
Changeset: a3c5b729592b
User: natefoo
Date: 2014-09-03 00:20:23
Summary: Remove the BuildSites class I recently created and merge its functionality back into util and the datatypes registry. As a result, the build_sites configuration moves to datatypes_conf.xml and display applications can now use configured build sites so that the path is not hardcoded into the individual committed display application files. `from_file` in the display applications is still supported.
Affected #: 25 files
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d datatypes_conf.xml.sample
--- a/datatypes_conf.xml.sample
+++ b/datatypes_conf.xml.sample
@@ -312,4 +312,12 @@
<sniffer type="galaxy.datatypes.assembly:Amos"/>
--></sniffers>
+ <build_sites>
+ <site type="ucsc" file="tool-data/shared/ucsc/ucsc_build_sites.txt"/>
+ <site type="gbrowse" file="tool-data/shared/gbrowse/gbrowse_build_sites.txt"/>
+ <site type="ensembl" file="tool-data/shared/ensembl/ensembl_sites.txt"/>
+ <site type="ensembl_data_url" file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt"/>
+ <site type="igv" file="tool-data/shared/igv/igv_build_sites.txt"/>
+ <site type="rviewer" file="tool-data/shared/rviewer/rviewer_build_sites.txt"/>
+ </build_sites></datatypes>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/ensembl/ensembl_bam.xml
--- a/display_applications/ensembl/ensembl_bam.xml
+++ b/display_applications/ensembl/ensembl_bam.xml
@@ -1,7 +1,7 @@
<display id="ensembl_bam" version="1.0.0" name="display at Ensembl"><!-- Current Ensembl method of attaching user data via URL; archives older than ~November 2008 will use a different method --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/ensembl/ensembl_gff.xml
--- a/display_applications/ensembl/ensembl_gff.xml
+++ b/display_applications/ensembl/ensembl_gff.xml
@@ -1,7 +1,7 @@
<display id="ensembl_gff" version="1.0.0" name="display at Ensembl"><!-- Current Ensembl method of attaching user data via URL; archives older than ~November 2008 will use a different method --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -41,7 +41,7 @@
<!-- Old Ensembl method of attaching user data via URL --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl_data_url" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/ensembl/ensembl_interval_as_bed.xml
--- a/display_applications/ensembl/ensembl_interval_as_bed.xml
+++ b/display_applications/ensembl/ensembl_interval_as_bed.xml
@@ -1,7 +1,7 @@
<display id="ensembl_interval" version="1.0.0" name="display at Ensembl"><!-- Current Ensembl method of attaching user data via URL; archives older than ~November 2008 will use a different method --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -41,7 +41,7 @@
<!-- Old Ensembl method of attaching user data via URL --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl_data_url" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/gbrowse/gbrowse_gff.xml
--- a/display_applications/gbrowse/gbrowse_gff.xml
+++ b/display_applications/gbrowse/gbrowse_gff.xml
@@ -1,6 +1,6 @@
<display id="gbrowse_gff" version="1.0.0" name="display at GBrowse"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="gbrowse" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/gbrowse/gbrowse_interval_as_bed.xml
--- a/display_applications/gbrowse/gbrowse_interval_as_bed.xml
+++ b/display_applications/gbrowse/gbrowse_interval_as_bed.xml
@@ -1,6 +1,6 @@
<display id="gbrowse_interval_as_bed" version="1.0.0" name="display at GBrowse"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="gbrowse" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/gbrowse/gbrowse_wig.xml
--- a/display_applications/gbrowse/gbrowse_wig.xml
+++ b/display_applications/gbrowse/gbrowse_wig.xml
@@ -1,6 +1,6 @@
<display id="gbrowse_wig" version="1.0.0" name="display at GBrowse"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="gbrowse" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/igv/bam.xml
--- a/display_applications/igv/bam.xml
+++ b/display_applications/igv/bam.xml
@@ -2,7 +2,7 @@
<display id="igv_bam" version="1.0.0" name="display with IGV"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/igv/igv_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="igv" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -92,4 +92,4 @@
</display>
-<!-- Dan Blankenberg -->
\ No newline at end of file
+<!-- Dan Blankenberg -->
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/igv/vcf.xml
--- a/display_applications/igv/vcf.xml
+++ b/display_applications/igv/vcf.xml
@@ -2,7 +2,7 @@
<display id="igv_vcf" version="1.0.0" name="display with IGV"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/igv/igv_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="igv" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -92,4 +92,4 @@
</display>
-<!-- Dan Blankenberg -->
\ No newline at end of file
+<!-- Dan Blankenberg -->
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/rviewer/bed.xml
--- a/display_applications/rviewer/bed.xml
+++ b/display_applications/rviewer/bed.xml
@@ -2,7 +2,7 @@
<display id="rviewer_interval" version="1.0.0" name="display at RViewer"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/rviewer/rviewer_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="rviewer" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/rviewer/vcf.xml
--- a/display_applications/rviewer/vcf.xml
+++ b/display_applications/rviewer/vcf.xml
@@ -2,7 +2,7 @@
<display id="rviewer_vcf" version="1.0.0" name="display at RViewer"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/rviewer/rviewer_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="rviewer" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/ucsc/bam.xml
--- a/display_applications/ucsc/bam.xml
+++ b/display_applications/ucsc/bam.xml
@@ -1,6 +1,6 @@
<display id="ucsc_bam" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/ucsc/bigbed.xml
--- a/display_applications/ucsc/bigbed.xml
+++ b/display_applications/ucsc/bigbed.xml
@@ -1,6 +1,6 @@
<display id="ucsc_bigbed" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/ucsc/bigwig.xml
--- a/display_applications/ucsc/bigwig.xml
+++ b/display_applications/ucsc/bigwig.xml
@@ -1,6 +1,6 @@
<display id="ucsc_bigwig" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/ucsc/interval_as_bed.xml
--- a/display_applications/ucsc/interval_as_bed.xml
+++ b/display_applications/ucsc/interval_as_bed.xml
@@ -1,6 +1,6 @@
<display id="ucsc_interval_as_bed" version="1.0.0" name="display at UCSC" inherit="True"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/ucsc/maf_customtrack.xml
--- a/display_applications/ucsc/maf_customtrack.xml
+++ b/display_applications/ucsc/maf_customtrack.xml
@@ -1,6 +1,6 @@
<display id="ucsc_maf_customtrack" version="1.0.0" name="display at UCSC" inherit="True"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d display_applications/ucsc/vcf.xml
--- a/display_applications/ucsc/vcf.xml
+++ b/display_applications/ucsc/vcf.xml
@@ -1,6 +1,6 @@
<display id="ucsc_vcf" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d lib/galaxy/app.py
--- a/lib/galaxy/app.py
+++ b/lib/galaxy/app.py
@@ -64,9 +64,6 @@
# Load dbkey / genome build manager
self._configure_genome_builds( data_table_name="__dbkeys__", load_old_style=True )
- # Load build sites (old-style)
- self._configure_build_sites()
-
# Genomes
self.genomes = Genomes( self )
# Data providers registry.
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -16,7 +16,6 @@
from galaxy.util import string_as_bool
from galaxy.util import listify
from galaxy.util.dbkeys import GenomeBuilds
-from galaxy.util.build_sites import BuildSites
from galaxy import eggs
log = logging.getLogger( __name__ )
@@ -224,9 +223,7 @@
self.sanitize_all_html = string_as_bool( kwargs.get( 'sanitize_all_html', True ) )
self.serve_xss_vulnerable_mimetypes = string_as_bool( kwargs.get( 'serve_xss_vulnerable_mimetypes', False ) )
self.enable_old_display_applications = string_as_bool( kwargs.get( "enable_old_display_applications", "True" ) )
- self.ucsc_build_sites = resolve_path( kwargs.get( 'ucsc_build_sites', os.path.join( self.tool_data_path, 'shared', 'ucsc', 'ucsc_build_sites.txt') ), self.root )
self.ucsc_display_sites = kwargs.get( 'ucsc_display_sites', "main,test,archaea,ucla" ).lower().split(",")
- self.gbrowse_build_sites = resolve_path( kwargs.get( 'gbrowse_build_sites', os.path.join( self.tool_data_path, 'shared', 'gbrowse', 'gbrowse_build_sites.txt') ), self.root )
self.gbrowse_display_sites = kwargs.get( 'gbrowse_display_sites', "modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225" ).lower().split(",")
self.brand = kwargs.get( 'brand', None )
self.welcome_url = kwargs.get( 'welcome_url', '/static/welcome.html' )
@@ -574,9 +571,6 @@
def _configure_genome_builds( self, data_table_name="__dbkeys__", load_old_style=True ):
self.genome_builds = GenomeBuilds( self, data_table_name=data_table_name, load_old_style=load_old_style )
- def _configure_build_sites( self ):
- self.build_sites = BuildSites( self )
-
def _configure_toolbox( self ):
# Initialize the tools, making sure the list of tool configs includes the reserved migrated_tools_conf.xml file.
tool_configs = self.config.tool_configs
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d lib/galaxy/datatypes/display_applications/application.py
--- a/lib/galaxy/datatypes/display_applications/application.py
+++ b/lib/galaxy/datatypes/display_applications/application.py
@@ -81,9 +81,13 @@
class DynamicDisplayApplicationBuilder( object ):
@classmethod
- def __init__( self, elem, display_application ):
+ def __init__( self, elem, display_application, build_sites ):
rval = []
- filename = elem.get( 'from_file', None )
+ filename = None
+ if elem.get( 'site_type', None ) is not None:
+ filename = build_sites.get( elem.get( 'site_type' ) )
+ else:
+ filename = elem.get( 'from_file', None )
assert filename is not None, 'Filename and id attributes required for dynamic_links'
skip_startswith = elem.get( 'skip_startswith', None )
separator = elem.get( 'separator', '\t' )
@@ -176,7 +180,7 @@
if link:
rval.links[ link.id ] = link
for dynamic_links in elem.findall( 'dynamic_links' ):
- for link in DynamicDisplayApplicationBuilder( dynamic_links, rval ):
+ for link in DynamicDisplayApplicationBuilder( dynamic_links, rval, datatypes_registry.build_sites ):
rval.links[ link.id ] = link
return rval
def __init__( self, display_id, name, datatypes_registry, version = None ):
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d lib/galaxy/datatypes/genetics.py
--- a/lib/galaxy/datatypes/genetics.py
+++ b/lib/galaxy/datatypes/genetics.py
@@ -85,7 +85,7 @@
if not dataset.dbkey:
dataset.dbkey = 'hg18' # punt!
if dataset.has_data():
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build(dataset.dbkey):
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey):
if site_name in app.config.ucsc_display_sites:
site_url = site_url.replace('/hgTracks?','/hgGenome?') # for genome graphs
internal_url = "%s" % url_for( controller='dataset',
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d lib/galaxy/datatypes/interval.py
--- a/lib/galaxy/datatypes/interval.py
+++ b/lib/galaxy/datatypes/interval.py
@@ -234,7 +234,7 @@
# Filter UCSC sites to only those that are supported by this build and
# enabled.
valid_sites = [ ( name, url )
- for name, url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey )
+ for name, url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey )
if name in app.config.ucsc_display_sites ]
if not valid_sites:
return []
@@ -750,7 +750,7 @@
ret_val = []
seqid, start, stop = self.get_estimated_display_viewport( dataset )
if seqid is not None:
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey ):
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey ):
if site_name in app.config.ucsc_display_sites:
redirect_url = urllib.quote_plus(
"%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" %
@@ -762,7 +762,7 @@
ret_val = []
seqid, start, stop = self.get_estimated_display_viewport( dataset )
if seqid is not None:
- for site_name, site_url in app.build_sites.get_gbrowse_sites_by_build( dataset.dbkey ):
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('gbrowse', dataset.dbkey ):
if site_name in app.config.gbrowse_display_sites:
if seqid.startswith( 'chr' ) and len ( seqid ) > 3:
seqid = seqid[3:]
@@ -1091,7 +1091,7 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport( dataset )
if chrom is not None:
- for site_name, site_url in app.build_sites.get_gbrowse_sites_by_build( dataset.dbkey ):
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('gbrowse', dataset.dbkey ):
if site_name in app.config.gbrowse_display_sites:
if chrom.startswith( 'chr' ) and len ( chrom ) > 3:
chrom = chrom[3:]
@@ -1103,7 +1103,7 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport( dataset )
if chrom is not None:
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey ):
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey ):
if site_name in app.config.ucsc_display_sites:
redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % ( site_url, dataset.dbkey, chrom, start, stop ) )
link = self._get_remote_call_url( redirect_url, site_name, dataset, type, app, base_url )
@@ -1285,7 +1285,7 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport(dataset)
if chrom is not None:
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build(dataset.dbkey):
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc',dataset.dbkey):
if site_name in app.config.ucsc_display_sites:
internal_url = "%s" % url_for( controller='dataset', dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name )
display_url = urllib.quote_plus( "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type) )
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -69,6 +69,9 @@
self.datatype_elems = []
self.sniffer_elems = []
self.xml_filename = None
+ # Build sites
+ self.build_sites = {}
+ self.legacy_build_sites = {}
def load_datatypes( self, root_dir=None, config=None, deactivate=False, override=True ):
"""
@@ -285,6 +288,8 @@
handling_proprietary_datatypes=handling_proprietary_datatypes,
override=override )
self.upload_file_formats.sort()
+ # Load build sites
+ self.load_build_sites( root )
# Persist the xml form of the registry into a temporary file so that it can be loaded from the command line by tools and
# set_metadata processing.
self.to_xml_file()
@@ -303,6 +308,26 @@
self.sniff_order.append( datatype )
append_to_sniff_order()
+ def load_build_sites( self, root ):
+ if root.find( 'build_sites' ):
+ for elem in root.find( 'build_sites' ).findall( 'site' ):
+ if not (elem.get( 'type' ) and elem.get( 'file' )):
+ self.log.exception( "Site is missing required 'type' and 'file' attributes: %s" )
+ else:
+ self.build_sites[elem.get( 'type' )] = elem.get( 'file' )
+ self.log.debug( "Loaded build site '%s': %s", elem.get( 'type' ), elem.get( 'file' ) )
+ for site, file in self.build_sites.items():
+ if site in ( 'ucsc', 'gbrowse' ):
+ self.legacy_build_sites[site] = galaxy.util.read_build_sites( file )
+ self.log.debug( "Loaded legacy build site '%s': %s", site, file )
+
+ def get_legacy_sites_by_build( self, site_type, build ):
+ sites = []
+ for site in self.legacy_build_sites[site_type]:
+ if build in site['builds']:
+ sites.append((site['name'], site['url']))
+ return sites
+
def load_datatype_sniffers( self, root, deactivate=False, handling_proprietary_datatypes=False, override=False ):
"""
Process the sniffers element from a parsed a datatypes XML file located at root_dir/config (if processing the Galaxy
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d lib/galaxy/util/__init__.py
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -829,6 +829,30 @@
return db_names
+def read_build_sites( filename, check_builds=True ):
+ """ read db names to ucsc mappings from file, this file should probably be merged with the one above """
+ build_sites = []
+ try:
+ for line in open(filename):
+ try:
+ if line[0:1] == "#":
+ continue
+ fields = line.replace("\r", "").replace("\n", "").split("\t")
+ site_name = fields[0]
+ site = fields[1]
+ if check_builds:
+ site_builds = fields[2].split(",")
+ site_dict = {'name': site_name, 'url': site, 'builds': site_builds}
+ else:
+ site_dict = {'name': site_name, 'url': site}
+ build_sites.append( site_dict )
+ except:
+ continue
+ except:
+ print "ERROR: Unable to read builds for site file %s" % filename
+ return build_sites
+
+
def relativize_symlinks( path, start=None, followlinks=False):
for root, dirs, files in os.walk( path, followlinks=followlinks ):
rel_start = None
diff -r 8e699330dfcd889b02acccb058f16b28c95ac848 -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d lib/galaxy/util/build_sites.py
--- a/lib/galaxy/util/build_sites.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
-Functionality for dealing with build sites for legacy display applications.
-"""
-import os.path
-
-
-class BuildSites( object ):
-
- def __init__( self, app ):
- self._app = app
- self._build_sites = {}
- self.load_build_sites()
-
- def read_build_sites( self, filename, check_builds=True ):
- """ read db names to ucsc mappings from file, this file should probably be merged with the one above """
- build_sites = []
- try:
- for line in open(filename):
- try:
- if line[0:1] == "#":
- continue
- fields = line.replace("\r", "").replace("\n", "").split("\t")
- site_name = fields[0]
- site = fields[1]
- if check_builds:
- site_builds = fields[2].split(",")
- site_dict = {'name': site_name, 'url': site, 'builds': site_builds}
- else:
- site_dict = {'name': site_name, 'url': site}
- build_sites.append( site_dict )
- except:
- continue
- except:
- print "ERROR: Unable to read builds for site file %s" % filename
- return build_sites
-
- def load_build_sites( self ):
- self._build_sites['ucsc'] = self.read_build_sites( self._app.config.ucsc_build_sites )
- self._build_sites['gbrowse'] = self.read_build_sites( self._app.config.gbrowse_build_sites )
-
- def _get_site_by_build( self, site_type, build ):
- sites = []
- for site in self._build_sites[site_type]:
- if build in site['builds']:
- sites.append((site['name'], site['url']))
- return sites
-
- def get_ucsc_sites_by_build( self, build ):
- return self._get_site_by_build( 'ucsc', build )
-
- def get_gbrowse_sites_by_build( self, build ):
- return self._get_site_by_build( 'gbrowse', build )
https://bitbucket.org/galaxy/galaxy-central/commits/6347caa946d7/
Changeset: 6347caa946d7
User: natefoo
Date: 2014-09-03 00:21:50
Summary: Remove config options from sample.
Affected #: 1 file
diff -r a3c5b729592b9f1d18a9e911a0125ba10f74c68d -r 6347caa946d76480267766122718db064ac3e0a1 universe_wsgi.ini.sample
--- a/universe_wsgi.ini.sample
+++ b/universe_wsgi.ini.sample
@@ -364,12 +364,10 @@
# which browsers should be available. URLs and builds available at these
# browsers are defined in the specifield files.
-# UCSC browsers:
-#ucsc_build_sites = tool-data/shared/ucsc/ucsc_build_sites.txt
+# UCSC browsers: tool-data/shared/ucsc/ucsc_build_sites.txt
#ucsc_display_sites = main,test,archaea,ucla
-# GBrowse servers:
-#gbrowse_build_sites = tool-data/shared/gbrowse/gbrowse_build_sites.txt
+# GBrowse servers: tool-data/shared/gbrowse/gbrowse_build_sites.txt
#gbrowse_display_sites = modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225
# If use_remote_user = True, display application servers will be denied access
https://bitbucket.org/galaxy/galaxy-central/commits/4888400b34d1/
Changeset: 4888400b34d1
User: natefoo
Date: 2014-09-03 18:25:22
Summary: Move ucsc_display_sites and gbrowse_display_sites into datatypes_conf.xml's new <build_sites> section, as per the suggestion by @nsoranzo.
Affected #: 10 files
diff -r 6347caa946d76480267766122718db064ac3e0a1 -r 4888400b34d120140d163ea3d92d053ba04e2ffb datatypes_conf.xml.sample
--- a/datatypes_conf.xml.sample
+++ b/datatypes_conf.xml.sample
@@ -313,8 +313,18 @@
--></sniffers><build_sites>
- <site type="ucsc" file="tool-data/shared/ucsc/ucsc_build_sites.txt"/>
- <site type="gbrowse" file="tool-data/shared/gbrowse/gbrowse_build_sites.txt"/>
+ <!--
+ Build sites define the builds (dbkeys) available at sites used by display
+ applications and the URL to those sites.
+
+ The `display` attributes on the `ucsc` and `gbrowse` sites replace the
+ `ucsc_display_sites` and `gbrowse_display_sites` options in
+ universe_wsgi.ini. Because these are used by "old-style" display
+ applications, their types cannot not change if you want the old-style
+ display links for these sites to work.
+ -->
+ <site type="ucsc" file="tool-data/shared/ucsc/ucsc_build_sites.txt" display="main,test,archaea,ucla"/>
+ <site type="gbrowse" file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" display="modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225"/><site type="ensembl" file="tool-data/shared/ensembl/ensembl_sites.txt"/><site type="ensembl_data_url" file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt"/><site type="igv" file="tool-data/shared/igv/igv_build_sites.txt"/>
diff -r 6347caa946d76480267766122718db064ac3e0a1 -r 4888400b34d120140d163ea3d92d053ba04e2ffb display_applications/gbrowse/gbrowse_gff.xml
--- a/display_applications/gbrowse/gbrowse_gff.xml
+++ b/display_applications/gbrowse/gbrowse_gff.xml
@@ -10,7 +10,7 @@
<dynamic_param name="site_organisms" value="4" split="True" separator="," /><!-- Filter out some of the links based upon matching site_id to a Galaxy application configuration parameter and by dataset dbkey -->
- <filter>${site_id in $APP.config.gbrowse_display_sites}</filter>
+ <filter>${site_id in $APP.datatypes_registry.get_display_sites('gbrowse')}</filter><filter>${dataset.dbkey in $site_dbkeys}</filter><!-- We define url and params as normal, but values defined in dynamic_param are available by specified name -->
diff -r 6347caa946d76480267766122718db064ac3e0a1 -r 4888400b34d120140d163ea3d92d053ba04e2ffb display_applications/gbrowse/gbrowse_interval_as_bed.xml
--- a/display_applications/gbrowse/gbrowse_interval_as_bed.xml
+++ b/display_applications/gbrowse/gbrowse_interval_as_bed.xml
@@ -10,7 +10,7 @@
<dynamic_param name="site_organisms" value="4" split="True" separator="," /><!-- Filter out some of the links based upon matching site_id to a Galaxy application configuration parameter and by dataset dbkey -->
- <filter>${site_id in $APP.config.gbrowse_display_sites}</filter>
+ <filter>${site_id in $APP.datatypes_registry.get_display_sites('gbrowse')}</filter><filter>${dataset.dbkey in $site_dbkeys}</filter><!-- We define url and params as normal, but values defined in dynamic_param are available by specified name -->
diff -r 6347caa946d76480267766122718db064ac3e0a1 -r 4888400b34d120140d163ea3d92d053ba04e2ffb display_applications/gbrowse/gbrowse_wig.xml
--- a/display_applications/gbrowse/gbrowse_wig.xml
+++ b/display_applications/gbrowse/gbrowse_wig.xml
@@ -10,7 +10,7 @@
<dynamic_param name="site_organisms" value="4" split="True" separator="," /><!-- Filter out some of the links based upon matching site_id to a Galaxy application configuration parameter and by dataset dbkey -->
- <filter>${site_id in $APP.config.gbrowse_display_sites}</filter>
+ <filter>${site_id in $APP.datatypes_registry.get_display_sites('gbrowse')}</filter><filter>${dataset.dbkey in $site_dbkeys}</filter><!-- We define url and params as normal, but values defined in dynamic_param are available by specified name -->
diff -r 6347caa946d76480267766122718db064ac3e0a1 -r 4888400b34d120140d163ea3d92d053ba04e2ffb lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -223,8 +223,6 @@
self.sanitize_all_html = string_as_bool( kwargs.get( 'sanitize_all_html', True ) )
self.serve_xss_vulnerable_mimetypes = string_as_bool( kwargs.get( 'serve_xss_vulnerable_mimetypes', False ) )
self.enable_old_display_applications = string_as_bool( kwargs.get( "enable_old_display_applications", "True" ) )
- self.ucsc_display_sites = kwargs.get( 'ucsc_display_sites', "main,test,archaea,ucla" ).lower().split(",")
- self.gbrowse_display_sites = kwargs.get( 'gbrowse_display_sites', "modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225" ).lower().split(",")
self.brand = kwargs.get( 'brand', None )
self.welcome_url = kwargs.get( 'welcome_url', '/static/welcome.html' )
# Configuration for the message box directly below the masthead.
diff -r 6347caa946d76480267766122718db064ac3e0a1 -r 4888400b34d120140d163ea3d92d053ba04e2ffb lib/galaxy/datatypes/genetics.py
--- a/lib/galaxy/datatypes/genetics.py
+++ b/lib/galaxy/datatypes/genetics.py
@@ -86,7 +86,7 @@
dataset.dbkey = 'hg18' # punt!
if dataset.has_data():
for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey):
- if site_name in app.config.ucsc_display_sites:
+ if site_name in datatypes_registry.get_display_sites('ucsc'):
site_url = site_url.replace('/hgTracks?','/hgGenome?') # for genome graphs
internal_url = "%s" % url_for( controller='dataset',
dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name )
diff -r 6347caa946d76480267766122718db064ac3e0a1 -r 4888400b34d120140d163ea3d92d053ba04e2ffb lib/galaxy/datatypes/interval.py
--- a/lib/galaxy/datatypes/interval.py
+++ b/lib/galaxy/datatypes/interval.py
@@ -235,7 +235,7 @@
# enabled.
valid_sites = [ ( name, url )
for name, url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey )
- if name in app.config.ucsc_display_sites ]
+ if name in app.datatypes_registry.get_display_sites('ucsc') ]
if not valid_sites:
return []
# If there are any valid sites, we need to generate the estimated
@@ -751,7 +751,7 @@
seqid, start, stop = self.get_estimated_display_viewport( dataset )
if seqid is not None:
for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey ):
- if site_name in app.config.ucsc_display_sites:
+ if site_name in app.datatypes_registry.get_display_sites('ucsc'):
redirect_url = urllib.quote_plus(
"%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" %
( site_url, dataset.dbkey, seqid, start, stop ) )
@@ -763,7 +763,7 @@
seqid, start, stop = self.get_estimated_display_viewport( dataset )
if seqid is not None:
for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('gbrowse', dataset.dbkey ):
- if site_name in app.config.gbrowse_display_sites:
+ if site_name in app.datatypes_registry.get_display_sites('gbrowse'):
if seqid.startswith( 'chr' ) and len ( seqid ) > 3:
seqid = seqid[3:]
redirect_url = urllib.quote_plus( "%s/?q=%s:%s..%s&eurl=%%s" % ( site_url, seqid, start, stop ) )
@@ -1092,7 +1092,7 @@
chrom, start, stop = self.get_estimated_display_viewport( dataset )
if chrom is not None:
for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('gbrowse', dataset.dbkey ):
- if site_name in app.config.gbrowse_display_sites:
+ if site_name in app.datatypes_registry.get_display_sites('gbrowse'):
if chrom.startswith( 'chr' ) and len ( chrom ) > 3:
chrom = chrom[3:]
redirect_url = urllib.quote_plus( "%s/?q=%s:%s..%s&eurl=%%s" % ( site_url, chrom, start, stop ) )
@@ -1104,7 +1104,7 @@
chrom, start, stop = self.get_estimated_display_viewport( dataset )
if chrom is not None:
for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey ):
- if site_name in app.config.ucsc_display_sites:
+ if site_name in app.datatypes_registry.get_display_sites('ucsc'):
redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % ( site_url, dataset.dbkey, chrom, start, stop ) )
link = self._get_remote_call_url( redirect_url, site_name, dataset, type, app, base_url )
ret_val.append( ( site_name, link ) )
@@ -1286,7 +1286,7 @@
chrom, start, stop = self.get_estimated_display_viewport(dataset)
if chrom is not None:
for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc',dataset.dbkey):
- if site_name in app.config.ucsc_display_sites:
+ if site_name in app.datatypes_registry.get_display_sites('ucsc'):
internal_url = "%s" % url_for( controller='dataset', dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name )
display_url = urllib.quote_plus( "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type) )
redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop ) )
diff -r 6347caa946d76480267766122718db064ac3e0a1 -r 4888400b34d120140d163ea3d92d053ba04e2ffb lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -71,6 +71,7 @@
self.xml_filename = None
# Build sites
self.build_sites = {}
+ self.display_sites = {}
self.legacy_build_sites = {}
def load_datatypes( self, root_dir=None, config=None, deactivate=False, override=True ):
@@ -314,20 +315,28 @@
if not (elem.get( 'type' ) and elem.get( 'file' )):
self.log.exception( "Site is missing required 'type' and 'file' attributes: %s" )
else:
- self.build_sites[elem.get( 'type' )] = elem.get( 'file' )
- self.log.debug( "Loaded build site '%s': %s", elem.get( 'type' ), elem.get( 'file' ) )
- for site, file in self.build_sites.items():
- if site in ( 'ucsc', 'gbrowse' ):
- self.legacy_build_sites[site] = galaxy.util.read_build_sites( file )
- self.log.debug( "Loaded legacy build site '%s': %s", site, file )
+ site_type = elem.get( 'type' )
+ file = elem.get( 'file' )
+ self.build_sites[site_type] = file
+ if site_type in ('ucsc', 'gbrowse'):
+ self.legacy_build_sites[site_type] = galaxy.util.read_build_sites( file )
+ if elem.get( 'display', None ):
+ display = elem.get( 'display' )
+ self.display_sites[site_type] = [ x.strip() for x in display.lower().split( ',' ) ]
+ self.log.debug( "Loaded build site '%s': %s with display sites: %s", site_type, file, display )
+ else:
+ self.log.debug( "Loaded build site '%s': %s", site_type, file )
def get_legacy_sites_by_build( self, site_type, build ):
sites = []
- for site in self.legacy_build_sites[site_type]:
+ for site in self.legacy_build_sites.get(site_type, []):
if build in site['builds']:
sites.append((site['name'], site['url']))
return sites
+ def get_display_sites( self, site_type ):
+ return self.display_sites.get( site_type, [] )
+
def load_datatype_sniffers( self, root, deactivate=False, handling_proprietary_datatypes=False, override=False ):
"""
Process the sniffers element from a parsed a datatypes XML file located at root_dir/config (if processing the Galaxy
diff -r 6347caa946d76480267766122718db064ac3e0a1 -r 4888400b34d120140d163ea3d92d053ba04e2ffb lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -926,7 +926,7 @@
url_for( controller='dataset', action='list' )
)
display_as = url_for( controller='root', action='display_as' )
- if self.app.config.ucsc_display_sites and self.request.path == display_as:
+ if self.app.datatypes_registry.get_display_sites('ucsc') and self.request.path == display_as:
try:
host = socket.gethostbyaddr( self.environ[ 'REMOTE_ADDR' ] )[0]
except( socket.error, socket.herror, socket.gaierror, socket.timeout ):
diff -r 6347caa946d76480267766122718db064ac3e0a1 -r 4888400b34d120140d163ea3d92d053ba04e2ffb universe_wsgi.ini.sample
--- a/universe_wsgi.ini.sample
+++ b/universe_wsgi.ini.sample
@@ -364,12 +364,6 @@
# which browsers should be available. URLs and builds available at these
# browsers are defined in the specifield files.
-# UCSC browsers: tool-data/shared/ucsc/ucsc_build_sites.txt
-#ucsc_display_sites = main,test,archaea,ucla
-
-# GBrowse servers: tool-data/shared/gbrowse/gbrowse_build_sites.txt
-#gbrowse_display_sites = modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225
-
# If use_remote_user = True, display application servers will be denied access
# to Galaxy and so displaying datasets in these sites will fail.
# display_servers contains a list of hostnames which should be allowed to
https://bitbucket.org/galaxy/galaxy-central/commits/608391566ac8/
Changeset: 608391566ac8
User: natefoo
Date: 2014-09-03 18:27:01
Summary: Fix nonstandard indentation.
Affected #: 1 file
diff -r 4888400b34d120140d163ea3d92d053ba04e2ffb -r 608391566ac80318f703ddf9b8f9506fc46dc866 lib/galaxy/datatypes/genetics.py
--- a/lib/galaxy/datatypes/genetics.py
+++ b/lib/galaxy/datatypes/genetics.py
@@ -83,29 +83,31 @@
ret_val = []
ggtail = 'hgGenome_doSubmitUpload=submit'
if not dataset.dbkey:
- dataset.dbkey = 'hg18' # punt!
+ dataset.dbkey = 'hg18' # punt!
if dataset.has_data():
- for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey):
- if site_name in datatypes_registry.get_display_sites('ucsc'):
- site_url = site_url.replace('/hgTracks?','/hgGenome?') # for genome graphs
- internal_url = "%s" % url_for( controller='dataset',
- dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name )
- display_url = "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type)
- display_url = urllib.quote_plus( display_url )
- # was display_url = urllib.quote_plus( "%s/display_as?id=%i&display_app=%s" % (base_url, dataset.id, type) )
- #redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop) )
- sl = ["%sdb=%s" % (site_url,dataset.dbkey ),]
- #sl.append("&hgt.customText=%s")
- sl.append("&hgGenome_dataSetName=%s&hgGenome_dataSetDescription=%s" % (dataset.name, 'GalaxyGG_data'))
- sl.append("&hgGenome_formatType=best guess&hgGenome_markerType=best guess")
- sl.append("&hgGenome_columnLabels=first row&hgGenome_maxVal=&hgGenome_labelVals=")
- sl.append("&hgGenome_doSubmitUpload=submit")
- sl.append("&hgGenome_maxGapToFill=25000000&hgGenome_uploadFile=%s" % display_url)
- s = ''.join(sl)
- s = urllib.quote_plus(s)
- redirect_url = s
- link = '%s?redirect_url=%s&display_url=%s' % ( internal_url, redirect_url, display_url )
- ret_val.append( (site_name, link) )
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey):
+ if site_name in datatypes_registry.get_display_sites('ucsc'):
+ site_url = site_url.replace('/hgTracks?','/hgGenome?') # for genome graphs
+ internal_url = "%s" % url_for( controller='dataset',
+ dataset_id=dataset.id,
+ action='display_at',
+ filename='ucsc_' + site_name )
+ display_url = "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type)
+ display_url = urllib.quote_plus( display_url )
+ # was display_url = urllib.quote_plus( "%s/display_as?id=%i&display_app=%s" % (base_url, dataset.id, type) )
+ #redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop) )
+ sl = ["%sdb=%s" % (site_url,dataset.dbkey ),]
+ #sl.append("&hgt.customText=%s")
+ sl.append("&hgGenome_dataSetName=%s&hgGenome_dataSetDescription=%s" % (dataset.name, 'GalaxyGG_data'))
+ sl.append("&hgGenome_formatType=best guess&hgGenome_markerType=best guess")
+ sl.append("&hgGenome_columnLabels=first row&hgGenome_maxVal=&hgGenome_labelVals=")
+ sl.append("&hgGenome_doSubmitUpload=submit")
+ sl.append("&hgGenome_maxGapToFill=25000000&hgGenome_uploadFile=%s" % display_url)
+ s = ''.join(sl)
+ s = urllib.quote_plus(s)
+ redirect_url = s
+ link = '%s?redirect_url=%s&display_url=%s' % ( internal_url, redirect_url, display_url )
+ ret_val.append( (site_name, link) )
return ret_val
def make_html_table( self, dataset, skipchars=[] ):
https://bitbucket.org/galaxy/galaxy-central/commits/f7e396d29268/
Changeset: f7e396d29268
User: natefoo
Date: 2014-09-03 19:13:48
Summary: Fix typo in sample config.
Affected #: 1 file
diff -r 608391566ac80318f703ddf9b8f9506fc46dc866 -r f7e396d292685cd03145225f40ad7eb6f74b9408 datatypes_conf.xml.sample
--- a/datatypes_conf.xml.sample
+++ b/datatypes_conf.xml.sample
@@ -320,8 +320,8 @@
The `display` attributes on the `ucsc` and `gbrowse` sites replace the
`ucsc_display_sites` and `gbrowse_display_sites` options in
universe_wsgi.ini. Because these are used by "old-style" display
- applications, their types cannot not change if you want the old-style
- display links for these sites to work.
+ applications, their types cannot change if you want the old-style display
+ links for these sites to work.
--><site type="ucsc" file="tool-data/shared/ucsc/ucsc_build_sites.txt" display="main,test,archaea,ucla"/><site type="gbrowse" file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" display="modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225"/>
https://bitbucket.org/galaxy/galaxy-central/commits/9b6cccb3af2d/
Changeset: 9b6cccb3af2d
User: natefoo
Date: 2014-09-04 17:38:26
Summary: Merged in natefoo/galaxy-central (pull request #481)
Make build sites files configurable
Affected #: 27 files
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 datatypes_conf.xml.sample
--- a/datatypes_conf.xml.sample
+++ b/datatypes_conf.xml.sample
@@ -312,4 +312,22 @@
<sniffer type="galaxy.datatypes.assembly:Amos"/>
--></sniffers>
+ <build_sites>
+ <!--
+ Build sites define the builds (dbkeys) available at sites used by display
+ applications and the URL to those sites.
+
+ The `display` attributes on the `ucsc` and `gbrowse` sites replace the
+ `ucsc_display_sites` and `gbrowse_display_sites` options in
+ universe_wsgi.ini. Because these are used by "old-style" display
+ applications, their types cannot change if you want the old-style display
+ links for these sites to work.
+ -->
+ <site type="ucsc" file="tool-data/shared/ucsc/ucsc_build_sites.txt" display="main,test,archaea,ucla"/>
+ <site type="gbrowse" file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" display="modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225"/>
+ <site type="ensembl" file="tool-data/shared/ensembl/ensembl_sites.txt"/>
+ <site type="ensembl_data_url" file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt"/>
+ <site type="igv" file="tool-data/shared/igv/igv_build_sites.txt"/>
+ <site type="rviewer" file="tool-data/shared/rviewer/rviewer_build_sites.txt"/>
+ </build_sites></datatypes>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ensembl/ensembl_bam.xml
--- a/display_applications/ensembl/ensembl_bam.xml
+++ b/display_applications/ensembl/ensembl_bam.xml
@@ -1,7 +1,7 @@
<display id="ensembl_bam" version="1.0.0" name="display at Ensembl"><!-- Current Ensembl method of attaching user data via URL; archives older than ~November 2008 will use a different method --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ensembl/ensembl_gff.xml
--- a/display_applications/ensembl/ensembl_gff.xml
+++ b/display_applications/ensembl/ensembl_gff.xml
@@ -1,7 +1,7 @@
<display id="ensembl_gff" version="1.0.0" name="display at Ensembl"><!-- Current Ensembl method of attaching user data via URL; archives older than ~November 2008 will use a different method --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -41,7 +41,7 @@
<!-- Old Ensembl method of attaching user data via URL --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl_data_url" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ensembl/ensembl_interval_as_bed.xml
--- a/display_applications/ensembl/ensembl_interval_as_bed.xml
+++ b/display_applications/ensembl/ensembl_interval_as_bed.xml
@@ -1,7 +1,7 @@
<display id="ensembl_interval" version="1.0.0" name="display at Ensembl"><!-- Current Ensembl method of attaching user data via URL; archives older than ~November 2008 will use a different method --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -41,7 +41,7 @@
<!-- Old Ensembl method of attaching user data via URL --><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ensembl/ensembl_sites_data_URL.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="ensembl_data_url" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/gbrowse/gbrowse_gff.xml
--- a/display_applications/gbrowse/gbrowse_gff.xml
+++ b/display_applications/gbrowse/gbrowse_gff.xml
@@ -1,6 +1,6 @@
<display id="gbrowse_gff" version="1.0.0" name="display at GBrowse"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="gbrowse" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -10,7 +10,7 @@
<dynamic_param name="site_organisms" value="4" split="True" separator="," /><!-- Filter out some of the links based upon matching site_id to a Galaxy application configuration parameter and by dataset dbkey -->
- <filter>${site_id in $APP.config.gbrowse_display_sites}</filter>
+ <filter>${site_id in $APP.datatypes_registry.get_display_sites('gbrowse')}</filter><filter>${dataset.dbkey in $site_dbkeys}</filter><!-- We define url and params as normal, but values defined in dynamic_param are available by specified name -->
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/gbrowse/gbrowse_interval_as_bed.xml
--- a/display_applications/gbrowse/gbrowse_interval_as_bed.xml
+++ b/display_applications/gbrowse/gbrowse_interval_as_bed.xml
@@ -1,6 +1,6 @@
<display id="gbrowse_interval_as_bed" version="1.0.0" name="display at GBrowse"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="gbrowse" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -10,7 +10,7 @@
<dynamic_param name="site_organisms" value="4" split="True" separator="," /><!-- Filter out some of the links based upon matching site_id to a Galaxy application configuration parameter and by dataset dbkey -->
- <filter>${site_id in $APP.config.gbrowse_display_sites}</filter>
+ <filter>${site_id in $APP.datatypes_registry.get_display_sites('gbrowse')}</filter><filter>${dataset.dbkey in $site_dbkeys}</filter><!-- We define url and params as normal, but values defined in dynamic_param are available by specified name -->
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/gbrowse/gbrowse_wig.xml
--- a/display_applications/gbrowse/gbrowse_wig.xml
+++ b/display_applications/gbrowse/gbrowse_wig.xml
@@ -1,6 +1,6 @@
<display id="gbrowse_wig" version="1.0.0" name="display at GBrowse"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/gbrowse/gbrowse_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="gbrowse" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -10,7 +10,7 @@
<dynamic_param name="site_organisms" value="4" split="True" separator="," /><!-- Filter out some of the links based upon matching site_id to a Galaxy application configuration parameter and by dataset dbkey -->
- <filter>${site_id in $APP.config.gbrowse_display_sites}</filter>
+ <filter>${site_id in $APP.datatypes_registry.get_display_sites('gbrowse')}</filter><filter>${dataset.dbkey in $site_dbkeys}</filter><!-- We define url and params as normal, but values defined in dynamic_param are available by specified name -->
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/igv/bam.xml
--- a/display_applications/igv/bam.xml
+++ b/display_applications/igv/bam.xml
@@ -2,7 +2,7 @@
<display id="igv_bam" version="1.0.0" name="display with IGV"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/igv/igv_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="igv" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -92,4 +92,4 @@
</display>
-<!-- Dan Blankenberg -->
\ No newline at end of file
+<!-- Dan Blankenberg -->
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/igv/vcf.xml
--- a/display_applications/igv/vcf.xml
+++ b/display_applications/igv/vcf.xml
@@ -2,7 +2,7 @@
<display id="igv_vcf" version="1.0.0" name="display with IGV"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/igv/igv_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="igv" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
@@ -92,4 +92,4 @@
</display>
-<!-- Dan Blankenberg -->
\ No newline at end of file
+<!-- Dan Blankenberg -->
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/rviewer/bed.xml
--- a/display_applications/rviewer/bed.xml
+++ b/display_applications/rviewer/bed.xml
@@ -2,7 +2,7 @@
<display id="rviewer_interval" version="1.0.0" name="display at RViewer"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/rviewer/rviewer_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="rviewer" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/rviewer/vcf.xml
--- a/display_applications/rviewer/vcf.xml
+++ b/display_applications/rviewer/vcf.xml
@@ -2,7 +2,7 @@
<display id="rviewer_vcf" version="1.0.0" name="display at RViewer"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/rviewer/rviewer_build_sites.txt" skip_startswith="#" id="0" name="1">
+ <dynamic_links site_type="rviewer" skip_startswith="#" id="0" name="1"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ucsc/bam.xml
--- a/display_applications/ucsc/bam.xml
+++ b/display_applications/ucsc/bam.xml
@@ -1,6 +1,6 @@
<display id="ucsc_bam" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ucsc/bigbed.xml
--- a/display_applications/ucsc/bigbed.xml
+++ b/display_applications/ucsc/bigbed.xml
@@ -1,6 +1,6 @@
<display id="ucsc_bigbed" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ucsc/bigwig.xml
--- a/display_applications/ucsc/bigwig.xml
+++ b/display_applications/ucsc/bigwig.xml
@@ -1,6 +1,6 @@
<display id="ucsc_bigwig" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ucsc/interval_as_bed.xml
--- a/display_applications/ucsc/interval_as_bed.xml
+++ b/display_applications/ucsc/interval_as_bed.xml
@@ -1,6 +1,6 @@
<display id="ucsc_interval_as_bed" version="1.0.0" name="display at UCSC" inherit="True"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ucsc/maf_customtrack.xml
--- a/display_applications/ucsc/maf_customtrack.xml
+++ b/display_applications/ucsc/maf_customtrack.xml
@@ -1,6 +1,6 @@
<display id="ucsc_maf_customtrack" version="1.0.0" name="display at UCSC" inherit="True"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 display_applications/ucsc/vcf.xml
--- a/display_applications/ucsc/vcf.xml
+++ b/display_applications/ucsc/vcf.xml
@@ -1,6 +1,6 @@
<display id="ucsc_vcf" version="1.0.0" name="display at UCSC"><!-- Load links from file: one line to one link -->
- <dynamic_links from_file="tool-data/shared/ucsc/ucsc_build_sites.txt" skip_startswith="#" id="0" name="0">
+ <dynamic_links site_type="ucsc" skip_startswith="#" id="0" name="0"><!-- Define parameters by column from file, allow splitting on builds --><dynamic_param name="site_id" value="0"/><dynamic_param name="ucsc_link" value="1"/>
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/app.py
--- a/lib/galaxy/app.py
+++ b/lib/galaxy/app.py
@@ -64,9 +64,6 @@
# Load dbkey / genome build manager
self._configure_genome_builds( data_table_name="__dbkeys__", load_old_style=True )
- # Load build sites (old-style)
- self._configure_build_sites()
-
# Genomes
self.genomes = Genomes( self )
# Data providers registry.
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -16,7 +16,6 @@
from galaxy.util import string_as_bool
from galaxy.util import listify
from galaxy.util.dbkeys import GenomeBuilds
-from galaxy.util.build_sites import BuildSites
from galaxy import eggs
log = logging.getLogger( __name__ )
@@ -224,10 +223,6 @@
self.sanitize_all_html = string_as_bool( kwargs.get( 'sanitize_all_html', True ) )
self.serve_xss_vulnerable_mimetypes = string_as_bool( kwargs.get( 'serve_xss_vulnerable_mimetypes', False ) )
self.enable_old_display_applications = string_as_bool( kwargs.get( "enable_old_display_applications", "True" ) )
- self.ucsc_build_sites = resolve_path( kwargs.get( 'ucsc_build_sites', os.path.join( self.tool_data_path, 'shared', 'ucsc', 'ucsc_build_sites.txt') ), self.root )
- self.ucsc_display_sites = kwargs.get( 'ucsc_display_sites', "main,test,archaea,ucla" ).lower().split(",")
- self.gbrowse_build_sites = resolve_path( kwargs.get( 'gbrowse_build_sites', os.path.join( self.tool_data_path, 'shared', 'gbrowse', 'gbrowse_build_sites.txt') ), self.root )
- self.gbrowse_display_sites = kwargs.get( 'gbrowse_display_sites', "modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225" ).lower().split(",")
self.brand = kwargs.get( 'brand', None )
self.welcome_url = kwargs.get( 'welcome_url', '/static/welcome.html' )
# Configuration for the message box directly below the masthead.
@@ -575,9 +570,6 @@
def _configure_genome_builds( self, data_table_name="__dbkeys__", load_old_style=True ):
self.genome_builds = GenomeBuilds( self, data_table_name=data_table_name, load_old_style=load_old_style )
- def _configure_build_sites( self ):
- self.build_sites = BuildSites( self )
-
def _configure_toolbox( self ):
# Initialize the tools, making sure the list of tool configs includes the reserved migrated_tools_conf.xml file.
tool_configs = self.config.tool_configs
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/datatypes/display_applications/application.py
--- a/lib/galaxy/datatypes/display_applications/application.py
+++ b/lib/galaxy/datatypes/display_applications/application.py
@@ -81,9 +81,13 @@
class DynamicDisplayApplicationBuilder( object ):
@classmethod
- def __init__( self, elem, display_application ):
+ def __init__( self, elem, display_application, build_sites ):
rval = []
- filename = elem.get( 'from_file', None )
+ filename = None
+ if elem.get( 'site_type', None ) is not None:
+ filename = build_sites.get( elem.get( 'site_type' ) )
+ else:
+ filename = elem.get( 'from_file', None )
assert filename is not None, 'Filename and id attributes required for dynamic_links'
skip_startswith = elem.get( 'skip_startswith', None )
separator = elem.get( 'separator', '\t' )
@@ -176,7 +180,7 @@
if link:
rval.links[ link.id ] = link
for dynamic_links in elem.findall( 'dynamic_links' ):
- for link in DynamicDisplayApplicationBuilder( dynamic_links, rval ):
+ for link in DynamicDisplayApplicationBuilder( dynamic_links, rval, datatypes_registry.build_sites ):
rval.links[ link.id ] = link
return rval
def __init__( self, display_id, name, datatypes_registry, version = None ):
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/datatypes/genetics.py
--- a/lib/galaxy/datatypes/genetics.py
+++ b/lib/galaxy/datatypes/genetics.py
@@ -83,29 +83,31 @@
ret_val = []
ggtail = 'hgGenome_doSubmitUpload=submit'
if not dataset.dbkey:
- dataset.dbkey = 'hg18' # punt!
+ dataset.dbkey = 'hg18' # punt!
if dataset.has_data():
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build(dataset.dbkey):
- if site_name in app.config.ucsc_display_sites:
- site_url = site_url.replace('/hgTracks?','/hgGenome?') # for genome graphs
- internal_url = "%s" % url_for( controller='dataset',
- dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name )
- display_url = "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type)
- display_url = urllib.quote_plus( display_url )
- # was display_url = urllib.quote_plus( "%s/display_as?id=%i&display_app=%s" % (base_url, dataset.id, type) )
- #redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop) )
- sl = ["%sdb=%s" % (site_url,dataset.dbkey ),]
- #sl.append("&hgt.customText=%s")
- sl.append("&hgGenome_dataSetName=%s&hgGenome_dataSetDescription=%s" % (dataset.name, 'GalaxyGG_data'))
- sl.append("&hgGenome_formatType=best guess&hgGenome_markerType=best guess")
- sl.append("&hgGenome_columnLabels=first row&hgGenome_maxVal=&hgGenome_labelVals=")
- sl.append("&hgGenome_doSubmitUpload=submit")
- sl.append("&hgGenome_maxGapToFill=25000000&hgGenome_uploadFile=%s" % display_url)
- s = ''.join(sl)
- s = urllib.quote_plus(s)
- redirect_url = s
- link = '%s?redirect_url=%s&display_url=%s' % ( internal_url, redirect_url, display_url )
- ret_val.append( (site_name, link) )
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey):
+ if site_name in datatypes_registry.get_display_sites('ucsc'):
+ site_url = site_url.replace('/hgTracks?','/hgGenome?') # for genome graphs
+ internal_url = "%s" % url_for( controller='dataset',
+ dataset_id=dataset.id,
+ action='display_at',
+ filename='ucsc_' + site_name )
+ display_url = "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type)
+ display_url = urllib.quote_plus( display_url )
+ # was display_url = urllib.quote_plus( "%s/display_as?id=%i&display_app=%s" % (base_url, dataset.id, type) )
+ #redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop) )
+ sl = ["%sdb=%s" % (site_url,dataset.dbkey ),]
+ #sl.append("&hgt.customText=%s")
+ sl.append("&hgGenome_dataSetName=%s&hgGenome_dataSetDescription=%s" % (dataset.name, 'GalaxyGG_data'))
+ sl.append("&hgGenome_formatType=best guess&hgGenome_markerType=best guess")
+ sl.append("&hgGenome_columnLabels=first row&hgGenome_maxVal=&hgGenome_labelVals=")
+ sl.append("&hgGenome_doSubmitUpload=submit")
+ sl.append("&hgGenome_maxGapToFill=25000000&hgGenome_uploadFile=%s" % display_url)
+ s = ''.join(sl)
+ s = urllib.quote_plus(s)
+ redirect_url = s
+ link = '%s?redirect_url=%s&display_url=%s' % ( internal_url, redirect_url, display_url )
+ ret_val.append( (site_name, link) )
return ret_val
def make_html_table( self, dataset, skipchars=[] ):
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/datatypes/interval.py
--- a/lib/galaxy/datatypes/interval.py
+++ b/lib/galaxy/datatypes/interval.py
@@ -234,8 +234,8 @@
# Filter UCSC sites to only those that are supported by this build and
# enabled.
valid_sites = [ ( name, url )
- for name, url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey )
- if name in app.config.ucsc_display_sites ]
+ for name, url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey )
+ if name in app.datatypes_registry.get_display_sites('ucsc') ]
if not valid_sites:
return []
# If there are any valid sites, we need to generate the estimated
@@ -750,8 +750,8 @@
ret_val = []
seqid, start, stop = self.get_estimated_display_viewport( dataset )
if seqid is not None:
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey ):
- if site_name in app.config.ucsc_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey ):
+ if site_name in app.datatypes_registry.get_display_sites('ucsc'):
redirect_url = urllib.quote_plus(
"%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" %
( site_url, dataset.dbkey, seqid, start, stop ) )
@@ -762,8 +762,8 @@
ret_val = []
seqid, start, stop = self.get_estimated_display_viewport( dataset )
if seqid is not None:
- for site_name, site_url in app.build_sites.get_gbrowse_sites_by_build( dataset.dbkey ):
- if site_name in app.config.gbrowse_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('gbrowse', dataset.dbkey ):
+ if site_name in app.datatypes_registry.get_display_sites('gbrowse'):
if seqid.startswith( 'chr' ) and len ( seqid ) > 3:
seqid = seqid[3:]
redirect_url = urllib.quote_plus( "%s/?q=%s:%s..%s&eurl=%%s" % ( site_url, seqid, start, stop ) )
@@ -1091,8 +1091,8 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport( dataset )
if chrom is not None:
- for site_name, site_url in app.build_sites.get_gbrowse_sites_by_build( dataset.dbkey ):
- if site_name in app.config.gbrowse_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('gbrowse', dataset.dbkey ):
+ if site_name in app.datatypes_registry.get_display_sites('gbrowse'):
if chrom.startswith( 'chr' ) and len ( chrom ) > 3:
chrom = chrom[3:]
redirect_url = urllib.quote_plus( "%s/?q=%s:%s..%s&eurl=%%s" % ( site_url, chrom, start, stop ) )
@@ -1103,8 +1103,8 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport( dataset )
if chrom is not None:
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey ):
- if site_name in app.config.ucsc_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey ):
+ if site_name in app.datatypes_registry.get_display_sites('ucsc'):
redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % ( site_url, dataset.dbkey, chrom, start, stop ) )
link = self._get_remote_call_url( redirect_url, site_name, dataset, type, app, base_url )
ret_val.append( ( site_name, link ) )
@@ -1285,8 +1285,8 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport(dataset)
if chrom is not None:
- for site_name, site_url in app.build_sites.get_ucsc_sites_by_build(dataset.dbkey):
- if site_name in app.config.ucsc_display_sites:
+ for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc',dataset.dbkey):
+ if site_name in app.datatypes_registry.get_display_sites('ucsc'):
internal_url = "%s" % url_for( controller='dataset', dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name )
display_url = urllib.quote_plus( "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type) )
redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop ) )
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -69,6 +69,10 @@
self.datatype_elems = []
self.sniffer_elems = []
self.xml_filename = None
+ # Build sites
+ self.build_sites = {}
+ self.display_sites = {}
+ self.legacy_build_sites = {}
def load_datatypes( self, root_dir=None, config=None, deactivate=False, override=True ):
"""
@@ -285,6 +289,8 @@
handling_proprietary_datatypes=handling_proprietary_datatypes,
override=override )
self.upload_file_formats.sort()
+ # Load build sites
+ self.load_build_sites( root )
# Persist the xml form of the registry into a temporary file so that it can be loaded from the command line by tools and
# set_metadata processing.
self.to_xml_file()
@@ -303,6 +309,34 @@
self.sniff_order.append( datatype )
append_to_sniff_order()
+ def load_build_sites( self, root ):
+ if root.find( 'build_sites' ):
+ for elem in root.find( 'build_sites' ).findall( 'site' ):
+ if not (elem.get( 'type' ) and elem.get( 'file' )):
+ self.log.exception( "Site is missing required 'type' and 'file' attributes: %s" )
+ else:
+ site_type = elem.get( 'type' )
+ file = elem.get( 'file' )
+ self.build_sites[site_type] = file
+ if site_type in ('ucsc', 'gbrowse'):
+ self.legacy_build_sites[site_type] = galaxy.util.read_build_sites( file )
+ if elem.get( 'display', None ):
+ display = elem.get( 'display' )
+ self.display_sites[site_type] = [ x.strip() for x in display.lower().split( ',' ) ]
+ self.log.debug( "Loaded build site '%s': %s with display sites: %s", site_type, file, display )
+ else:
+ self.log.debug( "Loaded build site '%s': %s", site_type, file )
+
+ def get_legacy_sites_by_build( self, site_type, build ):
+ sites = []
+ for site in self.legacy_build_sites.get(site_type, []):
+ if build in site['builds']:
+ sites.append((site['name'], site['url']))
+ return sites
+
+ def get_display_sites( self, site_type ):
+ return self.display_sites.get( site_type, [] )
+
def load_datatype_sniffers( self, root, deactivate=False, handling_proprietary_datatypes=False, override=False ):
"""
Process the sniffers element from a parsed a datatypes XML file located at root_dir/config (if processing the Galaxy
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/util/__init__.py
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -844,6 +844,30 @@
return db_names
+def read_build_sites( filename, check_builds=True ):
+ """ read db names to ucsc mappings from file, this file should probably be merged with the one above """
+ build_sites = []
+ try:
+ for line in open(filename):
+ try:
+ if line[0:1] == "#":
+ continue
+ fields = line.replace("\r", "").replace("\n", "").split("\t")
+ site_name = fields[0]
+ site = fields[1]
+ if check_builds:
+ site_builds = fields[2].split(",")
+ site_dict = {'name': site_name, 'url': site, 'builds': site_builds}
+ else:
+ site_dict = {'name': site_name, 'url': site}
+ build_sites.append( site_dict )
+ except:
+ continue
+ except:
+ print "ERROR: Unable to read builds for site file %s" % filename
+ return build_sites
+
+
def relativize_symlinks( path, start=None, followlinks=False):
for root, dirs, files in os.walk( path, followlinks=followlinks ):
rel_start = None
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/util/build_sites.py
--- a/lib/galaxy/util/build_sites.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
-Functionality for dealing with build sites for legacy display applications.
-"""
-import os.path
-
-
-class BuildSites( object ):
-
- def __init__( self, app ):
- self._app = app
- self._build_sites = {}
- self.load_build_sites()
-
- def read_build_sites( self, filename, check_builds=True ):
- """ read db names to ucsc mappings from file, this file should probably be merged with the one above """
- build_sites = []
- try:
- for line in open(filename):
- try:
- if line[0:1] == "#":
- continue
- fields = line.replace("\r", "").replace("\n", "").split("\t")
- site_name = fields[0]
- site = fields[1]
- if check_builds:
- site_builds = fields[2].split(",")
- site_dict = {'name': site_name, 'url': site, 'builds': site_builds}
- else:
- site_dict = {'name': site_name, 'url': site}
- build_sites.append( site_dict )
- except:
- continue
- except:
- print "ERROR: Unable to read builds for site file %s" % filename
- return build_sites
-
- def load_build_sites( self ):
- self._build_sites['ucsc'] = self.read_build_sites( self._app.config.ucsc_build_sites )
- self._build_sites['gbrowse'] = self.read_build_sites( self._app.config.gbrowse_build_sites )
-
- def _get_site_by_build( self, site_type, build ):
- sites = []
- for site in self._build_sites[site_type]:
- if build in site['builds']:
- sites.append((site['name'], site['url']))
- return sites
-
- def get_ucsc_sites_by_build( self, build ):
- return self._get_site_by_build( 'ucsc', build )
-
- def get_gbrowse_sites_by_build( self, build ):
- return self._get_site_by_build( 'gbrowse', build )
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -926,7 +926,7 @@
url_for( controller='dataset', action='list' )
)
display_as = url_for( controller='root', action='display_as' )
- if self.app.config.ucsc_display_sites and self.request.path == display_as:
+ if self.app.datatypes_registry.get_display_sites('ucsc') and self.request.path == display_as:
try:
host = socket.gethostbyaddr( self.environ[ 'REMOTE_ADDR' ] )[0]
except( socket.error, socket.herror, socket.gaierror, socket.timeout ):
diff -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf -r 9b6cccb3af2d6b13a1a1da7def87dcc19172be88 universe_wsgi.ini.sample
--- a/universe_wsgi.ini.sample
+++ b/universe_wsgi.ini.sample
@@ -364,14 +364,6 @@
# which browsers should be available. URLs and builds available at these
# browsers are defined in the specifield files.
-# UCSC browsers:
-#ucsc_build_sites = tool-data/shared/ucsc/ucsc_build_sites.txt
-#ucsc_display_sites = main,test,archaea,ucla
-
-# GBrowse servers:
-#gbrowse_build_sites = tool-data/shared/gbrowse/gbrowse_build_sites.txt
-#gbrowse_display_sites = modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225
-
# If use_remote_user = True, display application servers will be denied access
# to Galaxy and so displaying datasets in these sites will fail.
# display_servers contains a list of hostnames which should be allowed to
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
4 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/edc187004292/
Changeset: edc187004292
User: dannon
Date: 2014-09-04 01:38:44
Summary: Add is_uuid to lib/galaxy/util
Affected #: 1 file
diff -r 6070f4fbb38f88eb98cf65f0477924ffea9bc10c -r edc1870042923f32dfeb9dadcc13e1915edff825 lib/galaxy/util/__init__.py
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -98,6 +98,21 @@
return False
+def is_uuid( value ):
+ """
+ This method returns True if value is a UUID, otherwise False.
+ >>> is_uuid( "123e4567-e89b-12d3-a456-426655440000" )
+ True
+ >>> is_uuid( "0x3242340298902834" )
+ False
+ """
+ uuid_re = re.compile( "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" )
+ if re.match( uuid_re, str( value ) ):
+ return True
+ else:
+ return False
+
+
def get_charset_from_http_headers( headers, default=None ):
rval = headers.get('content-type', None )
if rval and 'charset=' in rval:
https://bitbucket.org/galaxy/galaxy-central/commits/8a93d99fccfa/
Changeset: 8a93d99fccfa
User: dannon
Date: 2014-09-04 01:49:05
Summary: pep8 prior to modifications / whitespace and comment spacing.
Affected #: 1 file
diff -r edc1870042923f32dfeb9dadcc13e1915edff825 -r 8a93d99fccfa452786836a568976b5ebb50cddf7 lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -439,17 +439,17 @@
stored_workflow = query.get( workflow_id )
except Exception:
try:
- #see if they have passed in the UUID for a workflow that is attached to a stored workflow
+ # see if they have passed in the UUID for a workflow that is attached to a stored workflow
workflow_uuid = uuid.UUID(workflow_id)
stored_workflow = trans.sa_session.query(trans.app.model.StoredWorkflow).filter( and_(
trans.app.model.StoredWorkflow.latest_workflow_id == trans.app.model.Workflow.id,
trans.app.model.Workflow.uuid == workflow_uuid
- )).first()
+ )).first()
if stored_workflow is None:
raise exceptions.ObjectNotFound( "Workflow not found: %s" % workflow_id )
return stored_workflow
except:
- pass #let the outer raise exception happen
+ pass # let the outer raise exception happen
raise exceptions.ObjectNotFound( "No such workflow found - invalid workflow identifier." )
if stored_workflow is None:
raise exceptions.ObjectNotFound( "No such workflow found." )
https://bitbucket.org/galaxy/galaxy-central/commits/75709a2361b1/
Changeset: 75709a2361b1
User: dannon
Date: 2014-09-04 16:59:44
Summary: Adjust __get_stored_workflow (by uuid) logic to detect UUIDs and switch accordingly.
Affected #: 1 file
diff -r 8a93d99fccfa452786836a568976b5ebb50cddf7 -r 75709a2361b15ba5fd393b0ff2333a81b6ddff87 lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -433,24 +433,19 @@
return stored_workflow
def __get_stored_workflow( self, trans, workflow_id ):
- try:
+ if util.is_uuid(workflow_id):
+ # see if they have passed in the UUID for a workflow that is attached to a stored workflow
+ workflow_uuid = uuid.UUID(workflow_id)
+ stored_workflow = trans.sa_session.query(trans.app.model.StoredWorkflow).filter( and_(
+ trans.app.model.StoredWorkflow.latest_workflow_id == trans.app.model.Workflow.id,
+ trans.app.model.Workflow.uuid == workflow_uuid
+ )).first()
+ if stored_workflow is None:
+ raise exceptions.ObjectNotFound( "Workflow not found: %s" % workflow_id )
+ else:
workflow_id = self.__decode_id( trans, workflow_id )
query = trans.sa_session.query( trans.app.model.StoredWorkflow )
stored_workflow = query.get( workflow_id )
- except Exception:
- try:
- # see if they have passed in the UUID for a workflow that is attached to a stored workflow
- workflow_uuid = uuid.UUID(workflow_id)
- stored_workflow = trans.sa_session.query(trans.app.model.StoredWorkflow).filter( and_(
- trans.app.model.StoredWorkflow.latest_workflow_id == trans.app.model.Workflow.id,
- trans.app.model.Workflow.uuid == workflow_uuid
- )).first()
- if stored_workflow is None:
- raise exceptions.ObjectNotFound( "Workflow not found: %s" % workflow_id )
- return stored_workflow
- except:
- pass # let the outer raise exception happen
- raise exceptions.ObjectNotFound( "No such workflow found - invalid workflow identifier." )
if stored_workflow is None:
raise exceptions.ObjectNotFound( "No such workflow found." )
return stored_workflow
https://bitbucket.org/galaxy/galaxy-central/commits/9fdca7477f44/
Changeset: 9fdca7477f44
User: dannon
Date: 2014-09-04 16:59:59
Summary: Merge.
Affected #: 11 files
diff -r 75709a2361b15ba5fd393b0ff2333a81b6ddff87 -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf .hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -18,4 +18,4 @@
81fbe25bd02edcd53065e8e4476dd1dfb5a72cf2 latest_2013.11.04
2a756ca2cb1826db7796018e77d12e2dd7b67603 latest_2014.02.10
ca45b78adb4152fc6e7395514d46eba6b7d0b838 release_2014.08.11
-ea12550fbc34260ae70bde38db59a4024f35f988 latest_2014.08.11
+20f4fdf1735aeff23a6e7ab00389781fe3f3142c latest_2014.08.11
diff -r 75709a2361b15ba5fd393b0ff2333a81b6ddff87 -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -2466,6 +2466,7 @@
val = getattr( ldda.datatype, name )
rval['metadata_' + name] = val
return rval
+
def get_template_widgets( self, trans, get_contents=True ):
# See if we have any associated templatesThe get_contents
# param is passed by callers that are inheriting a template - these
@@ -2492,6 +2493,7 @@
else:
return template.get_widgets( trans.user )
return []
+
def templates_dict( self, use_name=False ):
"""
Returns a dict of template info
@@ -2510,6 +2512,7 @@
tmp_dict[ name ] = content.get( field[ 'name' ] )
template_data[template.name] = tmp_dict
return template_data
+
def templates_json( self, use_name=False ):
return json.dumps( self.templates_dict( use_name=use_name ) )
@@ -2533,6 +2536,7 @@
self.info = info
self.inheritable = inheritable
+
class LibraryFolderInfoAssociation( object ):
def __init__( self, folder, form_definition, info, inheritable=False ):
self.folder = folder
@@ -2540,15 +2544,18 @@
self.info = info
self.inheritable = inheritable
+
class LibraryDatasetDatasetInfoAssociation( object ):
def __init__( self, library_dataset_dataset_association, form_definition, info ):
# TODO: need to figure out if this should be inheritable to the associated LibraryDataset
self.library_dataset_dataset_association = library_dataset_dataset_association
self.template = form_definition
self.info = info
+
@property
def inheritable( self ):
- return True #always allow inheriting, used for replacement
+ return True # always allow inheriting, used for replacement
+
class ValidationError( object ):
def __init__( self, message=None, err_type=None, attributes=None ):
@@ -2556,42 +2563,47 @@
self.err_type = err_type
self.attributes = attributes
+
class DatasetToValidationErrorAssociation( object ):
def __init__( self, dataset, validation_error ):
self.dataset = dataset
self.validation_error = validation_error
+
class ImplicitlyConvertedDatasetAssociation( object ):
- def __init__( self, id = None, parent = None, dataset = None, file_type = None, deleted = False, purged = False, metadata_safe = True ):
+
+ def __init__( self, id=None, parent=None, dataset=None, file_type=None, deleted=False, purged=False, metadata_safe=True ):
self.id = id
if isinstance(dataset, HistoryDatasetAssociation):
self.dataset = dataset
elif isinstance(dataset, LibraryDatasetDatasetAssociation):
self.dataset_ldda = dataset
else:
- raise AttributeError, 'Unknown dataset type provided for dataset: %s' % type( dataset )
+ raise AttributeError( 'Unknown dataset type provided for dataset: %s' % type( dataset ) )
if isinstance(parent, HistoryDatasetAssociation):
self.parent_hda = parent
elif isinstance(parent, LibraryDatasetDatasetAssociation):
self.parent_ldda = parent
else:
- raise AttributeError, 'Unknown dataset type provided for parent: %s' % type( parent )
+ raise AttributeError( 'Unknown dataset type provided for parent: %s' % type( parent ) )
self.type = file_type
self.deleted = deleted
self.purged = purged
self.metadata_safe = metadata_safe
- def clear( self, purge = False, delete_dataset = True ):
+ def clear( self, purge=False, delete_dataset=True ):
self.deleted = True
if self.dataset:
if delete_dataset:
self.dataset.deleted = True
if purge:
self.dataset.purged = True
- if purge and self.dataset.deleted: #do something with purging
+ if purge and self.dataset.deleted: # do something with purging
self.purged = True
- try: os.unlink( self.file_name )
- except Exception, e: print "Failed to purge associated file (%s) from disk: %s" % ( self.file_name, e )
+ try:
+ os.unlink( self.file_name )
+ except Exception, e:
+ print "Failed to purge associated file (%s) from disk: %s" % ( self.file_name, e )
DEFAULT_COLLECTION_NAME = "Unnamed Collection"
@@ -2903,6 +2915,7 @@
self.tool_id = None
self.message = message
+
class GalaxySession( object ):
def __init__( self,
id=None,
@@ -2924,24 +2937,29 @@
self.is_valid = is_valid
self.prev_session_id = prev_session_id
self.histories = []
+
def add_history( self, history, association=None ):
if association is None:
self.histories.append( GalaxySessionToHistoryAssociation( self, history ) )
else:
self.histories.append( association )
+
def get_disk_usage( self ):
if self.disk_usage is None:
return 0
return self.disk_usage
+
def set_disk_usage( self, bytes ):
self.disk_usage = bytes
total_disk_usage = property( get_disk_usage, set_disk_usage )
+
class GalaxySessionToHistoryAssociation( object ):
def __init__( self, galaxy_session, history ):
self.galaxy_session = galaxy_session
self.history = history
+
class UCI( object ):
def __init__( self ):
self.id = None
@@ -2962,14 +2980,14 @@
self.latest_workflow_id = None
self.workflows = []
- def copy_tags_from(self,target_user,source_workflow):
+ def copy_tags_from(self, target_user, source_workflow):
for src_swta in source_workflow.owner_tags:
new_swta = src_swta.copy()
new_swta.user = target_user
self.tags.append(new_swta)
- def to_dict( self, view='collection', value_mapper = None ):
- rval = super( StoredWorkflow, self ).to_dict( view=view, value_mapper = value_mapper )
+ def to_dict( self, view='collection', value_mapper=None ):
+ rval = super( StoredWorkflow, self ).to_dict( view=view, value_mapper=value_mapper )
tags_str_list = []
for tag in self.tags:
tag_str = tag.user_tname
@@ -3007,12 +3025,11 @@
return False
def to_dict( self, view='collection', value_mapper=None):
- rval = super( Workflow, self ).to_dict( view=view, value_mapper = value_mapper )
- rval['uuid'] = ( lambda uuid: str( uuid ) if uuid else None )( self.uuid )
+ rval = super( Workflow, self ).to_dict( view=view, value_mapper=value_mapper )
+ rval['uuid'] = ( lambda uuid: str( uuid ) if uuid else None )( self.uuid )
return rval
-
class WorkflowStep( object ):
def __init__( self ):
@@ -3061,7 +3078,7 @@
dict_collection_visible_keys = ( 'id', 'update_time', 'workflow_id' )
dict_element_visible_keys = ( 'id', 'update_time', 'workflow_id' )
- def to_dict( self, view='collection', value_mapper = None ):
+ def to_dict( self, view='collection', value_mapper=None ):
rval = super( WorkflowInvocation, self ).to_dict( view=view, value_mapper=value_mapper )
if view == 'element':
steps = {}
@@ -3072,12 +3089,12 @@
inputs = {}
for step in self.steps:
- if step.workflow_step.type =='tool':
+ if step.workflow_step.type == 'tool':
for step_input in step.workflow_step.input_connections:
if step_input.output_step.type == 'data_input':
for job_input in step.job.input_datasets:
if job_input.name == step_input.input_name:
- inputs[str(step_input.output_step.order_index)] = { "id" : job_input.dataset_id, "src" : "hda"}
+ inputs[str(step_input.output_step.order_index)] = { "id": job_input.dataset_id, "src": "hda"}
rval['inputs'] = inputs
return rval
@@ -3086,19 +3103,20 @@
dict_collection_visible_keys = ( 'id', 'update_time', 'job_id', 'workflow_step_id' )
dict_element_visible_keys = ( 'id', 'update_time', 'job_id', 'workflow_step_id' )
- def to_dict( self, view='collection', value_mapper = None ):
+ def to_dict( self, view='collection', value_mapper=None ):
rval = super( WorkflowInvocationStep, self ).to_dict( view=view, value_mapper=value_mapper )
rval['order_index'] = self.workflow_step.order_index
return rval
class MetadataFile( object ):
- def __init__( self, dataset = None, name = None ):
+ def __init__( self, dataset=None, name=None ):
if isinstance( dataset, HistoryDatasetAssociation ):
self.history_dataset = dataset
elif isinstance( dataset, LibraryDatasetDatasetAssociation ):
self.library_dataset = dataset
self.name = name
+
@property
def file_name( self ):
assert self.id is not None, "ID must be set before filename used (commit the object)"
@@ -3129,14 +3147,15 @@
class FormDefinition( object, Dictifiable ):
# The following form_builder classes are supported by the FormDefinition class.
supported_field_types = [ AddressField, CheckboxField, PasswordField, SelectField, TextArea, TextField, WorkflowField, WorkflowMappingField, HistoryField ]
- types = Bunch( REQUEST = 'Sequencing Request Form',
- SAMPLE = 'Sequencing Sample Form',
- EXTERNAL_SERVICE = 'External Service Information Form',
- RUN_DETAILS_TEMPLATE = 'Sample run details template',
- LIBRARY_INFO_TEMPLATE = 'Library information template',
- USER_INFO = 'User Information' )
+ types = Bunch( REQUEST='Sequencing Request Form',
+ SAMPLE='Sequencing Sample Form',
+ EXTERNAL_SERVICE='External Service Information Form',
+ RUN_DETAILS_TEMPLATE='Sample run details template',
+ LIBRARY_INFO_TEMPLATE='Library information template',
+ USER_INFO='User Information' )
dict_collection_visible_keys = ( 'id', 'name' )
dict_element_visible_keys = ( 'id', 'name', 'desc', 'form_definition_current_id', 'fields', 'layout' )
+
def __init__( self, name=None, desc=None, fields=[], form_definition_current=None, form_type=None, layout=None ):
self.name = name
self.desc = desc
@@ -3144,6 +3163,7 @@
self.form_definition_current = form_definition_current
self.type = form_type
self.layout = layout
+
def grid_fields( self, grid_index ):
# Returns a dictionary whose keys are integers corresponding to field positions
# on the grid and whose values are the field.
@@ -3152,6 +3172,7 @@
if str( f[ 'layout' ] ) == str( grid_index ):
gridfields[i] = f
return gridfields
+
def get_widgets( self, user, contents={}, **kwd ):
'''
Return the list of widgets that comprise a form definition,
@@ -3213,24 +3234,28 @@
field_widget.params = params
elif field_type == 'SelectField':
for option in field[ 'selectlist' ]:
+
if option == value:
field_widget.add_option( option, option, selected=True )
else:
field_widget.add_option( option, option )
elif field_type == 'CheckboxField':
+
field_widget.set_checked( value )
if field[ 'required' ] == 'required':
req = 'Required'
else:
req = 'Optional'
if field[ 'helptext' ]:
- helptext='%s (%s)' % ( field[ 'helptext' ], req )
+ helptext = '%s (%s)' % ( field[ 'helptext' ], req )
else:
helptext = '(%s)' % req
widgets.append( dict( label=field[ 'label' ],
+
widget=field_widget,
helptext=helptext ) )
return widgets
+
def field_as_html( self, field ):
"""Generates disabled html for a field"""
type = field[ 'type' ]
@@ -3245,21 +3270,25 @@
# Return None if unsupported field type
return None
+
class FormDefinitionCurrent( object ):
def __init__(self, form_definition=None):
self.latest_form = form_definition
+
class FormValues( object ):
def __init__(self, form_def=None, content=None):
self.form_definition = form_def
self.content = content
+
class Request( object, Dictifiable ):
- states = Bunch( NEW = 'New',
- SUBMITTED = 'In Progress',
- REJECTED = 'Rejected',
- COMPLETE = 'Complete' )
+ states = Bunch( NEW='New',
+ SUBMITTED='In Progress',
+ REJECTED='Rejected',
+ COMPLETE='Complete' )
dict_collection_visible_keys = ( 'id', 'name', 'state' )
+
def __init__( self, name=None, desc=None, request_type=None, user=None, form_values=None, notification=None ):
self.name = name
self.desc = desc
@@ -3268,17 +3297,20 @@
self.user = user
self.notification = notification
self.samples_list = []
+
@property
def state( self ):
latest_event = self.latest_event
if latest_event:
return latest_event.state
return None
+
@property
def latest_event( self ):
if self.events:
return self.events[0]
return None
+
@property
def samples_have_common_state( self ):
"""
@@ -3294,6 +3326,7 @@
if s.state.id != state_for_comparison.id:
return False
return state_for_comparison
+
@property
def last_comment( self ):
latest_event = self.latest_event
@@ -3302,26 +3335,34 @@
return latest_event.comment
return ''
return 'No comment'
+
def get_sample( self, sample_name ):
for sample in self.samples:
if sample.name == sample_name:
return sample
return None
+
@property
def is_unsubmitted( self ):
return self.state in [ self.states.REJECTED, self.states.NEW ]
+
@property
def is_rejected( self ):
return self.state == self.states.REJECTED
+
@property
def is_submitted( self ):
return self.state == self.states.SUBMITTED
+
@property
def is_new( self ):
+
return self.state == self.states.NEW
+
@property
def is_complete( self ):
return self.state == self.states.COMPLETE
+
@property
def samples_without_library_destinations( self ):
# Return all samples that are not associated with a library
@@ -3330,6 +3371,7 @@
if not sample.library:
samples.append( sample )
return samples
+
@property
def samples_with_bar_code( self ):
# Return all samples that have associated bar code
@@ -3338,6 +3380,7 @@
if sample.bar_code:
samples.append( sample )
return samples
+
def send_email_notification( self, trans, common_state, final_state=False ):
# Check if an email notification is configured to be sent when the samples
# are in this state
@@ -3390,7 +3433,7 @@
try:
send_mail( frm, to, subject, body, trans.app.config )
comments = "Email notification sent to %s." % ", ".join( to ).strip().strip( ',' )
- except Exception,e:
+ except Exception, e:
comments = "Email notification failed. (%s)" % str(e)
# update the request history with the email notification event
elif not trans.app.config.smtp_server:
@@ -3401,16 +3444,19 @@
trans.sa_session.flush()
return comments
+
class RequestEvent( object ):
def __init__(self, request=None, request_state=None, comment=''):
self.request = request
self.state = request_state
self.comment = comment
+
class ExternalService( object ):
- data_transfer_protocol = Bunch( HTTP = 'http',
- HTTPS = 'https',
- SCP = 'scp' )
+ data_transfer_protocol = Bunch( HTTP='http',
+ HTTPS='https',
+ SCP='scp' )
+
def __init__( self, name=None, description=None, external_service_type_id=None, version=None, form_definition_id=None, form_values_id=None, deleted=None ):
self.name = name
self.description = description
@@ -3419,9 +3465,11 @@
self.form_definition_id = form_definition_id
self.form_values_id = form_values_id
self.deleted = deleted
- self.label = None # Used in the request_type controller's __build_external_service_select_field() method
+ self.label = None # Used in the request_type controller's __build_external_service_select_field() method
+
def get_external_service_type( self, trans ):
return trans.app.external_service_types.all_external_service_types[ self.external_service_type_id ]
+
def load_data_transfer_settings( self, trans ):
trans.app.external_service_types.reload( self.external_service_type_id )
self.data_transfer = {}
@@ -3442,33 +3490,39 @@
automatic_transfer = data_transfer_obj.config.get( 'automatic_transfer', 'false' )
http_configs[ 'automatic_transfer' ] = galaxy.util.string_as_bool( automatic_transfer )
self.data_transfer[ self.data_transfer_protocol.HTTP ] = http_configs
+
def populate_actions( self, trans, item, param_dict=None ):
return self.get_external_service_type( trans ).actions.populate( self, item, param_dict=param_dict )
+
class RequestType( object, Dictifiable ):
dict_collection_visible_keys = ( 'id', 'name', 'desc' )
dict_element_visible_keys = ( 'id', 'name', 'desc', 'request_form_id', 'sample_form_id' )
- rename_dataset_options = Bunch( NO = 'Do not rename',
- SAMPLE_NAME = 'Preprend sample name',
- EXPERIMENT_NAME = 'Prepend experiment name',
- EXPERIMENT_AND_SAMPLE_NAME = 'Prepend experiment and sample name')
+ rename_dataset_options = Bunch( NO='Do not rename',
+ SAMPLE_NAME='Preprend sample name',
+ EXPERIMENT_NAME='Prepend experiment name',
+ EXPERIMENT_AND_SAMPLE_NAME='Prepend experiment and sample name')
permitted_actions = get_permitted_actions( filter='REQUEST_TYPE' )
+
def __init__( self, name=None, desc=None, request_form=None, sample_form=None ):
self.name = name
self.desc = desc
self.request_form = request_form
self.sample_form = sample_form
+
@property
def external_services( self ):
external_services = []
for rtesa in self.external_service_associations:
external_services.append( rtesa.external_service )
return external_services
+
def get_external_service( self, external_service_type_id ):
for rtesa in self.external_service_associations:
if rtesa.external_service.external_service_type_id == external_service_type_id:
return rtesa.external_service
return None
+
def get_external_services_for_manual_data_transfer( self, trans ):
'''Returns all external services that use manual data transfer'''
external_services = []
@@ -3481,6 +3535,7 @@
if not transfer_type_settings[ 'automatic_transfer' ]:
external_services.append( external_service )
return external_services
+
def delete_external_service_associations( self, trans ):
'''Deletes all external service associations.'''
flush_needed = False
@@ -3489,20 +3544,24 @@
flush_needed = True
if flush_needed:
trans.sa_session.flush()
+
def add_external_service_association( self, trans, external_service ):
rtesa = trans.model.RequestTypeExternalServiceAssociation( self, external_service )
trans.sa_session.add( rtesa )
trans.sa_session.flush()
+
@property
def final_sample_state( self ):
# The states mapper for this object orders ascending
return self.states[-1]
+
@property
def run_details( self ):
if self.run:
# self.run[0] is [RequestTypeRunAssociation]
return self.run[0]
return None
+
def get_template_widgets( self, trans, get_contents=True ):
# See if we have any associated templates. The get_contents param
# is passed by callers that are inheriting a template - these are
@@ -3520,23 +3579,27 @@
return template.get_widgets( trans.user )
return []
+
class RequestTypeExternalServiceAssociation( object ):
def __init__( self, request_type, external_service ):
self.request_type = request_type
self.external_service = external_service
+
class RequestTypePermissions( object ):
def __init__( self, action, request_type, role ):
self.action = action
self.request_type = request_type
self.role = role
+
class Sample( object, Dictifiable ):
# The following form_builder classes are supported by the Sample class.
supported_field_types = [ CheckboxField, SelectField, TextField, WorkflowField, WorkflowMappingField, HistoryField ]
- bulk_operations = Bunch( CHANGE_STATE = 'Change state',
- SELECT_LIBRARY = 'Select data library and folder' )
+ bulk_operations = Bunch( CHANGE_STATE='Change state',
+ SELECT_LIBRARY='Select data library and folder' )
dict_collection_visible_keys = ( 'id', 'name' )
+
def __init__(self, name=None, desc=None, request=None, form_values=None, bar_code=None, library=None, folder=None, workflow=None, history=None):
self.name = name
self.desc = desc
@@ -3547,17 +3610,20 @@
self.folder = folder
self.history = history
self.workflow = workflow
+
@property
def state( self ):
latest_event = self.latest_event
if latest_event:
return latest_event.state
return None
+
@property
def latest_event( self ):
if self.events:
return self.events[0]
return None
+
@property
def adding_to_library_dataset_files( self ):
adding_to_library_datasets = []
@@ -3565,6 +3631,7 @@
if dataset.status == SampleDataset.transfer_status.ADD_TO_LIBRARY:
adding_to_library_datasets.append( dataset )
return adding_to_library_datasets
+
@property
def inprogress_dataset_files( self ):
inprogress_datasets = []
@@ -3572,6 +3639,7 @@
if dataset.status not in [ SampleDataset.transfer_status.NOT_STARTED, SampleDataset.transfer_status.COMPLETE ]:
inprogress_datasets.append( dataset )
return inprogress_datasets
+
@property
def queued_dataset_files( self ):
queued_datasets = []
@@ -3579,6 +3647,7 @@
if dataset.status == SampleDataset.transfer_status.IN_QUEUE:
queued_datasets.append( dataset )
return queued_datasets
+
@property
def transfer_error_dataset_files( self ):
transfer_error_datasets = []
@@ -3586,6 +3655,7 @@
if dataset.status == SampleDataset.transfer_status.ERROR:
transfer_error_datasets.append( dataset )
return transfer_error_datasets
+
@property
def transferred_dataset_files( self ):
transferred_datasets = []
@@ -3593,6 +3663,7 @@
if dataset.status == SampleDataset.transfer_status.COMPLETE:
transferred_datasets.append( dataset )
return transferred_datasets
+
@property
def transferring_dataset_files( self ):
transferring_datasets = []
@@ -3600,6 +3671,7 @@
if dataset.status == SampleDataset.transfer_status.TRANSFERRING:
transferring_datasets.append( dataset )
return transferring_datasets
+
@property
def untransferred_dataset_files( self ):
untransferred_datasets = []
@@ -3607,6 +3679,7 @@
if dataset.status != SampleDataset.transfer_status.COMPLETE:
untransferred_datasets.append( dataset )
return untransferred_datasets
+
def get_untransferred_dataset_size( self, filepath, scp_configs ):
def print_ticks( d ):
pass
diff -r 75709a2361b15ba5fd393b0ff2333a81b6ddff87 -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -2130,6 +2130,8 @@
elif isinstance( value, basestring ):
if value.startswith( "dce:" ):
rval = trans.sa_session.query( trans.app.model.DatasetCollectionElement ).get( value[ len( "dce:"): ] )
+ elif value.startswith( "hdca:" ):
+ rval = trans.sa_session.query( trans.app.model.HistoryDatasetCollectionAssociation ).get( value[ len( "hdca:"): ] )
else:
rval = trans.sa_session.query( trans.app.model.HistoryDatasetCollectionAssociation ).get( value )
if rval and isinstance( rval, trans.app.model.HistoryDatasetCollectionAssociation ):
diff -r 75709a2361b15ba5fd393b0ff2333a81b6ddff87 -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf static/scripts/mvc/tools/tools-section.js
--- a/static/scripts/mvc/tools/tools-section.js
+++ b/static/scripts/mvc/tools/tools-section.js
@@ -336,7 +336,14 @@
id : 'field-' + id,
data : options,
value : options[0].value,
+ multiple : input_def.multiple,
onchange : function(value) {
+ // pick the first dataset if multiple might be selected
+ // TODO: iterate over all datasets and filter common/consistent columns
+ if (input_def.multiple) {
+ value = value[0];
+ }
+
// get referenced columns
var column_list = self.app.tree.findReferences(id, 'data_column');
@@ -437,18 +444,16 @@
// column selection field
_field_column : function (input_def) {
return new Ui.Select.View({
- id : 'field-' + input_def.id
+ id : 'field-' + input_def.id,
+ multiple: input_def.multiple
});
},
// text input field
_field_text : function(input_def) {
- var TextClass = Ui.Input;
- if (input_def.area) {
- TextClass = Ui.Textarea;
- }
- return new TextClass({
- id : 'field-' + input_def.id
+ return new Ui.Input({
+ id : 'field-' + input_def.id,
+ area : input_def.area
});
},
diff -r 75709a2361b15ba5fd393b0ff2333a81b6ddff87 -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf static/scripts/mvc/ui/ui-misc.js
--- a/static/scripts/mvc/ui/ui-misc.js
+++ b/static/scripts/mvc/ui/ui-misc.js
@@ -312,7 +312,8 @@
placeholder : '',
disabled : false,
visible : true,
- cls : ''
+ cls : '',
+ area : false
},
// initialize
@@ -352,60 +353,11 @@
// element
_template: function(options) {
- return '<input id="' + options.id + '" type="' + options.type + '" value="' + options.value + '" placeholder="' + options.placeholder + '" class="ui-input ' + options.cls + '">';
- }
-});
-
-// plugin
-var Textarea = Backbone.View.extend({
- // options
- optionsDefault: {
- value : '',
- type : 'text',
- placeholder : '',
- disabled : false,
- visible : true,
- cls : ''
- },
-
- // initialize
- initialize : function(options) {
- // configure options
- this.options = Utils.merge(options, this.optionsDefault);
-
- // create new element
- this.setElement(this._template(this.options));
-
- // disable input field
- if (this.options.disabled) {
- this.$el.prop('disabled', true);
+ if (options.area) {
+ return '<textarea id="' + options.id + '" class="ui-textarea ' + options.cls + '"></textarea>';
+ } else {
+ return '<input id="' + options.id + '" type="' + options.type + '" value="' + options.value + '" placeholder="' + options.placeholder + '" class="ui-input ' + options.cls + '">';
}
-
- // hide input field
- if (!this.options.visible) {
- this.$el.hide();
- }
-
- // onchange event handler. fires on user activity.
- var self = this;
- this.$el.on('input', function() {
- if (self.options.onchange) {
- self.options.onchange(self.$el.val());
- }
- });
- },
-
- // value
- value : function (new_val) {
- if (new_val !== undefined) {
- this.$el.val(new_val);
- }
- return this.$el.val();
- },
-
- // element
- _template: function(options) {
- return '<textarea id="' + options.id + '" class="ui-textarea ' + options.cls + '"></textarea>';
}
});
@@ -455,7 +407,6 @@
Checkbox : Checkbox,
Searchbox : Searchbox,
Select : Select,
- Textarea : Textarea,
Hidden : Hidden,
Slider : Slider
}
diff -r 75709a2361b15ba5fd393b0ff2333a81b6ddff87 -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf static/scripts/mvc/ui/ui-select-default.js
--- a/static/scripts/mvc/ui/ui-select-default.js
+++ b/static/scripts/mvc/ui/ui-select-default.js
@@ -7,24 +7,19 @@
var View = Backbone.View.extend({
// options
optionsDefault : {
- id : '',
- cls : '',
- empty : 'No data available',
- visible : true,
- wait : false
+ id : '',
+ cls : '',
+ empty : 'No data available',
+ visible : true,
+ wait : false,
+ multiple : false
},
- // value
- selected : null,
-
// initialize
initialize : function(options) {
// configure options
this.options = Utils.merge(options, this.optionsDefault);
- // initial value
- this.selected = this.options.value;
-
// create new element
this.setElement(this._template(this.options));
@@ -32,21 +27,17 @@
this.$select = this.$el.find('#select');
this.$icon = this.$el.find('#icon');
- // add change event. fires only on user activity
- var self = this;
- this.$select.on('change', function() {
- self.value(self.$select.val());
- });
-
- // add change event. fires on trigger
- this.on('change', function() {
- if (self.options.onchange) {
- self.options.onchange(self.value());
- }
- });
+ // configure multiple
+ if (this.options.multiple) {
+ this.$select.prop('multiple', true);
+ this.$select.addClass('ui-select-multiple');
+ this.$icon.remove();
+ } else {
+ this.$el.addClass('ui-select');
+ }
// refresh
- this._refresh();
+ this.update(this.options.data);
// show/hide
if (!this.options.visible) {
@@ -59,31 +50,25 @@
} else {
this.show();
}
+
+ // add change event. fires only on user activity
+ var self = this;
+ this.$select.on('change', function() {
+ self._change();
+ });
+
+ // add change event. fires on trigger
+ this.on('change', function() {
+ self._change();
+ });
},
// value
value : function (new_value) {
-
- // get current id/value
- var before = this.selected;
-
- // check if new_value is defined
if (new_value !== undefined) {
- this.selected = new_value;
this.$select.val(new_value);
}
-
- // get current id/value
- var after = this.selected;
- if (after) {
- // fire onchange
- if (after != before && this.options.onchange) {
- this.options.onchange(after);
- }
- }
-
- // return
- return after;
+ return this.$select.val();
},
// first
@@ -157,6 +142,9 @@
// render
update: function(options) {
+ // backup current value
+ var current = this.$select.val();
+
// remove all options
this.$select.find('option').remove();
@@ -167,6 +155,14 @@
// refresh
this._refresh();
+
+ // set previous value
+ this.$select.val(current);
+
+ // check if any value was set
+ if (!this.$select.val()) {
+ this.$select.val(this.first());
+ }
},
// set on change event
@@ -179,6 +175,13 @@
return this.$select.find('option[value=' + value + ']').length > 0;
},
+ // change
+ _change: function() {
+ if (this.options.onchange) {
+ this.options.onchange(this.$select.val());
+ }
+ },
+
// refresh
_refresh: function() {
// remove placeholder
@@ -196,41 +199,21 @@
// enable select field
this.enable();
}
-
- // update value
- if (this.selected) {
- this.$select.val(this.selected);
- }
},
- // option
+ // template option
_templateOption: function(options) {
return '<option value="' + options.value + '">' + options.label + '</option>';
},
- // element
+ // template
_template: function(options) {
- var tmpl = '<div id="' + options.id + '" class="ui-select">' +
- '<div class="button">' +
- '<i id="icon"/>' +
- '</div>' +
- '<select id="select" class="select ' + options.cls + ' ' + options.id + '">';
- for (key in options.data) {
- // options
- var item = options.data[key];
-
- // identify selected value
- var tag = '';
- if (item.value == options.value || item.value == '') {
- tag = 'selected';
- }
-
- // add template string
- tmpl += '<option value="' + item.value + '" ' + tag + '>' + item.label + '</option>';
- }
- tmpl += '</select>' +
- '</div>';
- return tmpl;
+ return '<div id="' + options.id + '">' +
+ '<div class="button">' +
+ '<i id="icon"/>' +
+ '</div>' +
+ '<select id="select" class="select ' + options.cls + ' ' + options.id + '"></select>' +
+ '</div>';
}
});
diff -r 75709a2361b15ba5fd393b0ff2333a81b6ddff87 -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf static/scripts/packed/mvc/tools/tools-section.js
--- a/static/scripts/packed/mvc/tools/tools-section.js
+++ b/static/scripts/packed/mvc/tools/tools-section.js
@@ -1,1 +1,1 @@
-define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-tabs"],function(c,b,e,a){var d=Backbone.View.extend({initialize:function(g,f){this.app=g;this.inputs=f.inputs;f.cls_tr="section-row";this.table=new b.View(f);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var f in this.inputs){this._add(this.inputs[f])}},_add:function(h){var g=this;var f=jQuery.extend(true,{},h);f.id=c.uuid();this.app.input_list[f.id]=f;var i=f.type;switch(i){case"conditional":this._addConditional(f);break;case"repeat":this._addRepeat(f);break;default:this._addRow(i,f)}},_addConditional:function(f){f.label=f.test_param.label;f.value=f.test_param.value;this._addRow("conditional",f);for(var h in f.cases){var g=f.id+"-section-"+h;var j=new d(this.app,{inputs:f.cases[h].inputs,cls:"ui-table-plain"});this.table.add("");this.table.add(j.$el);this.table.append(g)}},_addRepeat:function(f){var g=this;var k=new a.View({title_new:"Add "+f.title,max:f.max,onnew:function(){var i=f.id+"-section-"+c.uuid();var m=new d(g.app,{inputs:f.inputs,cls:"ui-table-plain"});k.add({id:i,title:f.title,$el:m.$el,ondel:function(){k.del(i);k.retitle(f.title);g.app.refresh()}});k.retitle(f.title);k.show(i);g.app.refresh()}});for(var j=0;j<f.min;j++){var h=f.id+"-section-"+c.uuid();var l=new d(g.app,{inputs:f.inputs,cls:"ui-table-plain"});k.add({id:h,title:f.title,$el:l.$el})}k.retitle(f.title);this.table.add("");this.table.add(k.$el);this.table.append(f.id)},_addRow:function(h,f){var j=f.id;var g=null;switch(h){case"text":g=this._field_text(f);break;case"select":g=this._field_select(f);break;case"data":g=this._field_data(f);break;case"data_column":g=this._field_column(f);break;case"conditional":g=this._field_conditional(f);break;case"hidden":g=this._field_hidden(f);break;case"integer":g=this._field_slider(f);break;case"float":g=this._field_slider(f);break;case"boolean":g=this._field_boolean(f);break;default:g=this._field_text(f);console.debug("tools-form::_addRow() : Unmatched field type ("+h+").")}if(f.value!==undefined){g.value(f.value)}this.app.field_list[j]=g;var i=$("<div/>");i.append(g.$el);if(f.help){i.append('<div class="ui-table-form-info">'+f.help+"</div>")}this.table.add('<span class="ui-table-form-title">'+f.label+"</span>","20%");this.table.add(i);this.table.append(j)},_field_conditional:function(f){var g=this;var h=[];for(var j in f.test_param.options){var k=f.test_param.options[j];h.push({label:k[0],value:k[1]})}return new e.Select.View({id:"field-"+f.id,data:h,onchange:function(s){for(var q in f.cases){var m=f.cases[q];var p=f.id+"-section-"+q;var l=g.table.get(p);var o=false;for(var n in m.inputs){var r=m.inputs[n].type;if(r&&r!=="hidden"){o=true;break}}if(m.value==s&&o){l.fadeIn("fast")}else{l.hide()}}}})},_field_data:function(f){var g=this;var l=f.id;var k=this.app.datasets.filterType();var h=[];for(var j in k){h.push({label:k[j].get("name"),value:k[j].get("id")})}return new e.Select.View({id:"field-"+l,data:h,value:h[0].value,onchange:function(u){var s=g.app.tree.findReferences(l,"data_column");var n=g.app.datasets.filter(u);if(n&&s.length>0){console.debug("tool-form::field_data() - Selected dataset "+u+".");var w=n.get("metadata_column_types");if(!w){console.debug("tool-form::field_data() - FAILED: Could not find metadata for dataset "+u+".")}for(var p in s){var q=g.app.input_list[s[p]];var r=g.app.field_list[s[p]];if(!q||!r){console.debug("tool-form::field_data() - FAILED: Column not found.")}var o=q.numerical;var m=[];for(var v in w){var t=w[v];if(t=="int"||t=="float"||!o){m.push({label:"Column: "+(parseInt(v)+1)+" ["+w[v]+"]",value:v})}}if(r){r.update(m);if(!r.exists(r.value())){r.value(r.first())}}}}else{console.debug("tool-form::field_data() - FAILED: Could not find dataset "+u+".")}}})},_field_select:function(f){var g=[];for(var h in f.options){var j=f.options[h];g.push({label:j[0],value:j[1]})}var k=e.Select;switch(f.display){case"checkboxes":k=e.Checkbox;break;case"radio":k=e.RadioButton;break}if(f.multiple){k=e.Checkbox}return new k.View({id:"field-"+f.id,data:g})},_field_column:function(f){return new e.Select.View({id:"field-"+f.id})},_field_text:function(g){var f=e.Input;if(g.area){f=e.Textarea}return new f({id:"field-"+g.id})},_field_slider:function(f){return new e.Slider.View({id:"field-"+f.id,min:f.min||0,max:f.max||1000,decimal:f.type=="float"})},_field_hidden:function(f){return new e.Hidden({id:"field-"+f.id})},_field_boolean:function(f){return new e.RadioButton.View({id:"field-"+f.id,data:[{label:"Yes",value:true},{label:"No",value:false}]})}});return{View:d}});
\ No newline at end of file
+define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-tabs"],function(c,b,e,a){var d=Backbone.View.extend({initialize:function(g,f){this.app=g;this.inputs=f.inputs;f.cls_tr="section-row";this.table=new b.View(f);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var f in this.inputs){this._add(this.inputs[f])}},_add:function(h){var g=this;var f=jQuery.extend(true,{},h);f.id=c.uuid();this.app.input_list[f.id]=f;var i=f.type;switch(i){case"conditional":this._addConditional(f);break;case"repeat":this._addRepeat(f);break;default:this._addRow(i,f)}},_addConditional:function(f){f.label=f.test_param.label;f.value=f.test_param.value;this._addRow("conditional",f);for(var h in f.cases){var g=f.id+"-section-"+h;var j=new d(this.app,{inputs:f.cases[h].inputs,cls:"ui-table-plain"});this.table.add("");this.table.add(j.$el);this.table.append(g)}},_addRepeat:function(f){var g=this;var k=new a.View({title_new:"Add "+f.title,max:f.max,onnew:function(){var i=f.id+"-section-"+c.uuid();var m=new d(g.app,{inputs:f.inputs,cls:"ui-table-plain"});k.add({id:i,title:f.title,$el:m.$el,ondel:function(){k.del(i);k.retitle(f.title);g.app.refresh()}});k.retitle(f.title);k.show(i);g.app.refresh()}});for(var j=0;j<f.min;j++){var h=f.id+"-section-"+c.uuid();var l=new d(g.app,{inputs:f.inputs,cls:"ui-table-plain"});k.add({id:h,title:f.title,$el:l.$el})}k.retitle(f.title);this.table.add("");this.table.add(k.$el);this.table.append(f.id)},_addRow:function(h,f){var j=f.id;var g=null;switch(h){case"text":g=this._field_text(f);break;case"select":g=this._field_select(f);break;case"data":g=this._field_data(f);break;case"data_column":g=this._field_column(f);break;case"conditional":g=this._field_conditional(f);break;case"hidden":g=this._field_hidden(f);break;case"integer":g=this._field_slider(f);break;case"float":g=this._field_slider(f);break;case"boolean":g=this._field_boolean(f);break;default:g=this._field_text(f);console.debug("tools-form::_addRow() : Unmatched field type ("+h+").")}if(f.value!==undefined){g.value(f.value)}this.app.field_list[j]=g;var i=$("<div/>");i.append(g.$el);if(f.help){i.append('<div class="ui-table-form-info">'+f.help+"</div>")}this.table.add('<span class="ui-table-form-title">'+f.label+"</span>","20%");this.table.add(i);this.table.append(j)},_field_conditional:function(f){var g=this;var h=[];for(var j in f.test_param.options){var k=f.test_param.options[j];h.push({label:k[0],value:k[1]})}return new e.Select.View({id:"field-"+f.id,data:h,onchange:function(s){for(var q in f.cases){var m=f.cases[q];var p=f.id+"-section-"+q;var l=g.table.get(p);var o=false;for(var n in m.inputs){var r=m.inputs[n].type;if(r&&r!=="hidden"){o=true;break}}if(m.value==s&&o){l.fadeIn("fast")}else{l.hide()}}}})},_field_data:function(f){var g=this;var l=f.id;var k=this.app.datasets.filterType();var h=[];for(var j in k){h.push({label:k[j].get("name"),value:k[j].get("id")})}return new e.Select.View({id:"field-"+l,data:h,value:h[0].value,onchange:function(u){var s=g.app.tree.findReferences(l,"data_column");var n=g.app.datasets.filter(u);if(n&&s.length>0){console.debug("tool-form::field_data() - Selected dataset "+u+".");var w=n.get("metadata_column_types");if(!w){console.debug("tool-form::field_data() - FAILED: Could not find metadata for dataset "+u+".")}for(var p in s){var q=g.app.input_list[s[p]];var r=g.app.field_list[s[p]];if(!q||!r){console.debug("tool-form::field_data() - FAILED: Column not found.")}var o=q.numerical;var m=[];for(var v in w){var t=w[v];if(t=="int"||t=="float"||!o){m.push({label:"Column: "+(parseInt(v)+1)+" ["+w[v]+"]",value:v})}}if(r){r.update(m);if(!r.exists(r.value())){r.value(r.first())}}}}else{console.debug("tool-form::field_data() - FAILED: Could not find dataset "+u+".")}}})},_field_select:function(f){var g=[];for(var h in f.options){var j=f.options[h];g.push({label:j[0],value:j[1]})}var k=e.Select;switch(f.display){case"checkboxes":k=e.Checkbox;break;case"radio":k=e.RadioButton;break}if(f.multiple){k=e.Checkbox}return new k.View({id:"field-"+f.id,data:g})},_field_column:function(f){return new e.Select.View({id:"field-"+f.id,multiple:f.multiple})},_field_text:function(f){return new e.Input({id:"field-"+f.id,area:f.area})},_field_slider:function(f){return new e.Slider.View({id:"field-"+f.id,min:f.min||0,max:f.max||1000,decimal:f.type=="float"})},_field_hidden:function(f){return new e.Hidden({id:"field-"+f.id})},_field_boolean:function(f){return new e.RadioButton.View({id:"field-"+f.id,data:[{label:"Yes",value:true},{label:"No",value:false}]})}});return{View:d}});
\ No newline at end of file
diff -r 75709a2361b15ba5fd393b0ff2333a81b6ddff87 -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf static/scripts/packed/mvc/ui/ui-misc.js
--- a/static/scripts/packed/mvc/ui/ui-misc.js
+++ b/static/scripts/packed/mvc/ui/ui-misc.js
@@ -1,1 +1,1 @@
-define(["utils/utils","mvc/ui/ui-select-default","mvc/ui/ui-slider","mvc/ui/ui-checkbox","mvc/ui/ui-radiobutton","mvc/ui/ui-button-menu","mvc/ui/ui-modal"],function(e,h,k,i,b,r,c){var q=Backbone.View.extend({optionsDefault:{url:"",cls:""},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options))},_template:function(s){return'<img class="ui-image '+s.cls+'" src="'+s.url+'"/>'}});var l=Backbone.View.extend({optionsDefault:{title:"",cls:""},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options))},title:function(s){this.$el.html(s)},_template:function(s){return'<label class="ui-label '+s.cls+'">'+s.title+"</label>"},value:function(){return options.title}});var d=Backbone.View.extend({optionsDefault:{floating:"right",icon:"",tooltip:"",placement:"bottom",title:"",cls:""},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).tooltip({title:s.tooltip,placement:"bottom"})},_template:function(s){return'<div><span class="fa '+s.icon+'" class="ui-icon"/> '+s.title+"</div>"}});var g=Backbone.View.extend({optionsDefault:{id:null,title:"",floating:"right",cls:"btn btn-default",icon:""},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).on("click",s.onclick);$(this.el).tooltip({title:s.tooltip,placement:"bottom"})},_template:function(s){var t='<button id="'+s.id+'" type="submit" style="float: '+s.floating+';" type="button" class="ui-button '+s.cls+'">';if(s.icon){t+='<i class="icon fa '+s.icon+'"></i> '}t+=s.title+"</button>";return t}});var o=Backbone.View.extend({optionsDefault:{id:null,title:"",floating:"right",cls:"icon-btn",icon:"",tooltip:"",onclick:null},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).on("click",s.onclick);$(this.el).tooltip({title:s.tooltip,placement:"bottom"})},_template:function(s){var t="";if(s.title){t="width: auto;"}var u='<div id="'+s.id+'" style="float: '+s.floating+"; "+t+'" class="ui-button-icon '+s.cls+'">';if(s.title){u+='<div class="button"><i class="icon fa '+s.icon+'"/> <span class="title">'+s.title+"</span></div>"}else{u+='<i class="icon fa '+s.icon+'"/>'}u+="</div>";return u}});var p=Backbone.View.extend({optionsDefault:{title:"",cls:""},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).on("click",s.onclick)},_template:function(s){return'<div><a href="javascript:void(0)" class="ui-anchor '+s.cls+'">'+s.title+"</a></div>"}});var a=Backbone.View.extend({optionsDefault:{message:"",status:"info",persistent:false},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement("<div></div>")},update:function(t){this.options=e.merge(t,this.optionsDefault);if(t.message!=""){this.$el.html(this._template(this.options));this.$el.find(".alert").append(t.message);this.$el.fadeIn();if(!t.persistent){var s=this;window.setTimeout(function(){if(s.$el.is(":visible")){s.$el.fadeOut()}else{s.$el.hide()}},3000)}}else{this.$el.fadeOut()}},_template:function(s){return'<div class="ui-message alert alert-'+s.status+'"/>'}});var f=Backbone.View.extend({optionsDefault:{onclick:null,searchword:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault);this.setElement(this._template(this.options));var s=this;if(this.options.onclick){this.$el.on("submit",function(v){var u=s.$el.find("#search");s.options.onclick(u.val())})}},_template:function(s){return'<div class="ui-search"><form onsubmit="return false;"><input id="search" class="form-control input-sm" type="text" name="search" placeholder="Search..." value="'+s.searchword+'"><button type="submit" class="btn search-btn"><i class="fa fa-search"></i></button></form></div>'}});var n=Backbone.View.extend({optionsDefault:{value:"",type:"text",placeholder:"",disabled:false,visible:true,cls:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault);this.setElement(this._template(this.options));if(this.options.disabled){this.$el.prop("disabled",true)}if(!this.options.visible){this.$el.hide()}var s=this;this.$el.on("input",function(){if(s.options.onchange){s.options.onchange(s.$el.val())}})},value:function(s){if(s!==undefined){this.$el.val(s)}return this.$el.val()},_template:function(s){return'<input id="'+s.id+'" type="'+s.type+'" value="'+s.value+'" placeholder="'+s.placeholder+'" class="ui-input '+s.cls+'">'}});var j=Backbone.View.extend({optionsDefault:{value:"",type:"text",placeholder:"",disabled:false,visible:true,cls:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault);this.setElement(this._template(this.options));if(this.options.disabled){this.$el.prop("disabled",true)}if(!this.options.visible){this.$el.hide()}var s=this;this.$el.on("input",function(){if(s.options.onchange){s.options.onchange(s.$el.val())}})},value:function(s){if(s!==undefined){this.$el.val(s)}return this.$el.val()},_template:function(s){return'<textarea id="'+s.id+'" class="ui-textarea '+s.cls+'"></textarea>'}});var m=Backbone.View.extend({optionsDefault:{value:""},initialize:function(s){this.options=e.merge(s,this.optionsDefault);this.setElement(this._template(this.options))},value:function(s){if(s!==undefined){this.$el.val(s)}return this.$el.val()},_template:function(s){return'<hidden id="'+s.id+'" value="'+s.value+'"/>'}});return{Anchor:p,Button:g,ButtonIcon:o,ButtonMenu:r,Icon:d,Image:q,Input:n,Label:l,Message:a,Modal:c,RadioButton:b,Checkbox:i,Searchbox:f,Select:h,Textarea:j,Hidden:m,Slider:k}});
\ No newline at end of file
+define(["utils/utils","mvc/ui/ui-select-default","mvc/ui/ui-slider","mvc/ui/ui-checkbox","mvc/ui/ui-radiobutton","mvc/ui/ui-button-menu","mvc/ui/ui-modal"],function(l,b,f,e,m,q,n){var d=Backbone.View.extend({optionsDefault:{url:"",cls:""},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options))},_template:function(r){return'<img class="ui-image '+r.cls+'" src="'+r.url+'"/>'}});var k=Backbone.View.extend({optionsDefault:{title:"",cls:""},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options))},title:function(r){this.$el.html(r)},_template:function(r){return'<label class="ui-label '+r.cls+'">'+r.title+"</label>"},value:function(){return options.title}});var c=Backbone.View.extend({optionsDefault:{floating:"right",icon:"",tooltip:"",placement:"bottom",title:"",cls:""},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).tooltip({title:r.tooltip,placement:"bottom"})},_template:function(r){return'<div><span class="fa '+r.icon+'" class="ui-icon"/> '+r.title+"</div>"}});var h=Backbone.View.extend({optionsDefault:{id:null,title:"",floating:"right",cls:"btn btn-default",icon:""},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).on("click",r.onclick);$(this.el).tooltip({title:r.tooltip,placement:"bottom"})},_template:function(r){var s='<button id="'+r.id+'" type="submit" style="float: '+r.floating+';" type="button" class="ui-button '+r.cls+'">';if(r.icon){s+='<i class="icon fa '+r.icon+'"></i> '}s+=r.title+"</button>";return s}});var i=Backbone.View.extend({optionsDefault:{id:null,title:"",floating:"right",cls:"icon-btn",icon:"",tooltip:"",onclick:null},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).on("click",r.onclick);$(this.el).tooltip({title:r.tooltip,placement:"bottom"})},_template:function(r){var s="";if(r.title){s="width: auto;"}var t='<div id="'+r.id+'" style="float: '+r.floating+"; "+s+'" class="ui-button-icon '+r.cls+'">';if(r.title){t+='<div class="button"><i class="icon fa '+r.icon+'"/> <span class="title">'+r.title+"</span></div>"}else{t+='<i class="icon fa '+r.icon+'"/>'}t+="</div>";return t}});var g=Backbone.View.extend({optionsDefault:{title:"",cls:""},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options));$(this.el).on("click",r.onclick)},_template:function(r){return'<div><a href="javascript:void(0)" class="ui-anchor '+r.cls+'">'+r.title+"</a></div>"}});var o=Backbone.View.extend({optionsDefault:{message:"",status:"info",persistent:false},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement("<div></div>")},update:function(s){this.options=l.merge(s,this.optionsDefault);if(s.message!=""){this.$el.html(this._template(this.options));this.$el.find(".alert").append(s.message);this.$el.fadeIn();if(!s.persistent){var r=this;window.setTimeout(function(){if(r.$el.is(":visible")){r.$el.fadeOut()}else{r.$el.hide()}},3000)}}else{this.$el.fadeOut()}},_template:function(r){return'<div class="ui-message alert alert-'+r.status+'"/>'}});var a=Backbone.View.extend({optionsDefault:{onclick:null,searchword:""},initialize:function(s){this.options=l.merge(s,this.optionsDefault);this.setElement(this._template(this.options));var r=this;if(this.options.onclick){this.$el.on("submit",function(u){var t=r.$el.find("#search");r.options.onclick(t.val())})}},_template:function(r){return'<div class="ui-search"><form onsubmit="return false;"><input id="search" class="form-control input-sm" type="text" name="search" placeholder="Search..." value="'+r.searchword+'"><button type="submit" class="btn search-btn"><i class="fa fa-search"></i></button></form></div>'}});var j=Backbone.View.extend({optionsDefault:{value:"",type:"text",placeholder:"",disabled:false,visible:true,cls:"",area:false},initialize:function(s){this.options=l.merge(s,this.optionsDefault);this.setElement(this._template(this.options));if(this.options.disabled){this.$el.prop("disabled",true)}if(!this.options.visible){this.$el.hide()}var r=this;this.$el.on("input",function(){if(r.options.onchange){r.options.onchange(r.$el.val())}})},value:function(r){if(r!==undefined){this.$el.val(r)}return this.$el.val()},_template:function(r){if(r.area){return'<textarea id="'+r.id+'" class="ui-textarea '+r.cls+'"></textarea>'}else{return'<input id="'+r.id+'" type="'+r.type+'" value="'+r.value+'" placeholder="'+r.placeholder+'" class="ui-input '+r.cls+'">'}}});var p=Backbone.View.extend({optionsDefault:{value:""},initialize:function(r){this.options=l.merge(r,this.optionsDefault);this.setElement(this._template(this.options))},value:function(r){if(r!==undefined){this.$el.val(r)}return this.$el.val()},_template:function(r){return'<hidden id="'+r.id+'" value="'+r.value+'"/>'}});return{Anchor:g,Button:h,ButtonIcon:i,ButtonMenu:q,Icon:c,Image:d,Input:j,Label:k,Message:o,Modal:n,RadioButton:m,Checkbox:e,Searchbox:a,Select:b,Hidden:p,Slider:f}});
\ No newline at end of file
diff -r 75709a2361b15ba5fd393b0ff2333a81b6ddff87 -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf static/scripts/packed/mvc/ui/ui-select-default.js
--- a/static/scripts/packed/mvc/ui/ui-select-default.js
+++ b/static/scripts/packed/mvc/ui/ui-select-default.js
@@ -1,1 +1,1 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{id:"",cls:"",empty:"No data available",visible:true,wait:false},selected:null,initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.selected=this.options.value;this.setElement(this._template(this.options));this.$select=this.$el.find("#select");this.$icon=this.$el.find("#icon");var c=this;this.$select.on("change",function(){c.value(c.$select.val())});this.on("change",function(){if(c.options.onchange){c.options.onchange(c.value())}});this._refresh();if(!this.options.visible){this.hide()}if(this.options.wait){this.wait()}else{this.show()}},value:function(c){var d=this.selected;if(c!==undefined){this.selected=c;this.$select.val(c)}var e=this.selected;if(e){if(e!=d&&this.options.onchange){this.options.onchange(e)}}return e},first:function(){var c=this.$select.find("option");if(c.length>0){return c.val()}else{return undefined}},text:function(){return this.$select.find("option:selected").text()},show:function(){this.$icon.removeClass();this.$icon.addClass("fa fa-caret-down");this.$select.show();this.$el.show()},hide:function(){this.$el.hide()},wait:function(){this.$icon.removeClass();this.$icon.addClass("fa fa-spinner fa-spin");this.$select.hide()},disabled:function(){return this.$select.is(":disabled")},enable:function(){this.$select.prop("disabled",false)},disable:function(){this.$select.prop("disabled",true)},add:function(c){this.$select.append(this._templateOption(c));this._refresh()},del:function(c){this.$select.find("option[value="+c+"]").remove();this.$select.trigger("change");this._refresh()},update:function(c){this.$select.find("option").remove();for(var d in c){this.$select.append(this._templateOption(c[d]))}this._refresh()},setOnChange:function(c){this.options.onchange=c},exists:function(c){return this.$select.find("option[value="+c+"]").length>0},_refresh:function(){this.$select.find("option[value=null]").remove();var c=this.$select.find("option").length;if(c==0){this.disable();this.$select.append(this._templateOption({value:"null",label:this.options.empty}))}else{this.enable()}if(this.selected){this.$select.val(this.selected)}},_templateOption:function(c){return'<option value="'+c.value+'">'+c.label+"</option>"},_template:function(e){var d='<div id="'+e.id+'" class="ui-select"><div class="button"><i id="icon"/></div><select id="select" class="select '+e.cls+" "+e.id+'">';for(key in e.data){var f=e.data[key];var c="";if(f.value==e.value||f.value==""){c="selected"}d+='<option value="'+f.value+'" '+c+">"+f.label+"</option>"}d+="</select></div>";return d}});return{View:b}});
\ No newline at end of file
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{id:"",cls:"",empty:"No data available",visible:true,wait:false,multiple:false},initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.$select=this.$el.find("#select");this.$icon=this.$el.find("#icon");if(this.options.multiple){this.$select.prop("multiple",true);this.$select.addClass("ui-select-multiple");this.$icon.remove()}else{this.$el.addClass("ui-select")}this.update(this.options.data);if(!this.options.visible){this.hide()}if(this.options.wait){this.wait()}else{this.show()}var c=this;this.$select.on("change",function(){c._change()});this.on("change",function(){c._change()})},value:function(c){if(c!==undefined){this.$select.val(c)}return this.$select.val()},first:function(){var c=this.$select.find("option");if(c.length>0){return c.val()}else{return undefined}},text:function(){return this.$select.find("option:selected").text()},show:function(){this.$icon.removeClass();this.$icon.addClass("fa fa-caret-down");this.$select.show();this.$el.show()},hide:function(){this.$el.hide()},wait:function(){this.$icon.removeClass();this.$icon.addClass("fa fa-spinner fa-spin");this.$select.hide()},disabled:function(){return this.$select.is(":disabled")},enable:function(){this.$select.prop("disabled",false)},disable:function(){this.$select.prop("disabled",true)},add:function(c){this.$select.append(this._templateOption(c));this._refresh()},del:function(c){this.$select.find("option[value="+c+"]").remove();this.$select.trigger("change");this._refresh()},update:function(c){var e=this.$select.val();this.$select.find("option").remove();for(var d in c){this.$select.append(this._templateOption(c[d]))}this._refresh();this.$select.val(e);if(!this.$select.val()){this.$select.val(this.first())}},setOnChange:function(c){this.options.onchange=c},exists:function(c){return this.$select.find("option[value="+c+"]").length>0},_change:function(){if(this.options.onchange){this.options.onchange(this.$select.val())}},_refresh:function(){this.$select.find("option[value=null]").remove();var c=this.$select.find("option").length;if(c==0){this.disable();this.$select.append(this._templateOption({value:"null",label:this.options.empty}))}else{this.enable()}},_templateOption:function(c){return'<option value="'+c.value+'">'+c.label+"</option>"},_template:function(c){return'<div id="'+c.id+'"><div class="button"><i id="icon"/></div><select id="select" class="select '+c.cls+" "+c.id+'"></select></div>'}});return{View:b}});
\ No newline at end of file
diff -r 75709a2361b15ba5fd393b0ff2333a81b6ddff87 -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -1282,6 +1282,7 @@
.ui-table tbody{cursor:pointer}
.ui-table-plain tbody td{padding:5px 0px 5px 5px !important;border:none !important}
.ui-table-plain tbody{cursor:auto !important}
+.ui-table-form-info{clear:both !important}
.ui-table-form-separator{font-weight:bold;font-size:0.9em}
.ui-label{font-weight:bold}
.ui-message{padding:2px 2px 2px 10px}
@@ -1299,8 +1300,9 @@
.ui-portlet .no-scroll{height:calc(100% - 80px)}
.ui-popover{max-width:700px;display:none}.ui-popover .popover-close{position:absolute;right:10px;top:7px;font-size:1.2em;cursor:pointer}
.ui-popover .popover-title{padding:4px 10px}
-.ui-select{position:relative;height:27px;overflow:hidden;border:1px solid #bfbfbf;-moz-border-radius:3px;border-radius:3px}.ui-select .button{position:relative;width:25px;height:100%;float:right;border-left:1px solid #bfbfbf;padding-left:9px;padding-top:4px;background:#f2f2f2}
+.ui-select,.ui-select-multiple{position:relative;height:27px;overflow:hidden;border:1px solid #bfbfbf;-moz-border-radius:3px;border-radius:3px}.ui-select .button{position:relative;width:25px;height:100%;float:right;border-left:1px solid #bfbfbf;padding-left:9px;padding-top:4px;background:#f2f2f2}
.ui-select select{position:absolute;top:0px;height:100%;width:100%;padding-left:5px;cursor:pointer;background:transparent;border:0;border-radius:0;-webkit-appearance:none}
+.ui-select-multiple{height:100% !important;width:100% !important}
.libraryRow{background-color:#ebd9b2}
.datasetHighlighted{background-color:#f9f9f9}
.libraryItemDeleted-True{font-style:italic}
diff -r 75709a2361b15ba5fd393b0ff2333a81b6ddff87 -r 9fdca7477f44165ae894177a2fef78a4ff9dc5bf static/style/src/less/ui.less
--- a/static/style/src/less/ui.less
+++ b/static/style/src/less/ui.less
@@ -32,6 +32,7 @@
.ui-table-form-info {
&:extend(.toolParamHelp);
+ clear: both !important;
}
.ui-table-form-separator {
@@ -208,4 +209,10 @@
border-radius: 0;
-webkit-appearance: none;
}
+}
+
+.ui-select-multiple {
+ &:extend(.ui-select);
+ height: 100% !important;
+ width: 100% !important;
}
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0