galaxy-dev
Threads by month
- ----- 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
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
April 2010
- 37 participants
- 148 discussions
15 Apr '10
details: http://www.bx.psu.edu/hg/galaxy/rev/1486617bfae8
changeset: 3652:1486617bfae8
user: Nate Coraor <nate(a)bx.psu.edu>
date: Wed Apr 14 14:09:01 2010 -0400
description:
Don't display the divider in the user tab if using external authentication
diffstat:
templates/webapps/galaxy/base_panels.mako | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diffs (13 lines):
diff -r 9de680276272 -r 1486617bfae8 templates/webapps/galaxy/base_panels.mako
--- a/templates/webapps/galaxy/base_panels.mako Wed Apr 14 14:04:03 2010 -0400
+++ b/templates/webapps/galaxy/base_panels.mako Wed Apr 14 14:09:01 2010 -0400
@@ -123,8 +123,8 @@
<li><a target="galaxy_main" href="${h.url_for( controller='/user', action='dbkeys' )}">Custom Builds</a></li>
%endif
<li><a target="_top" href="${logout_url}">Logout</a></li>
+ <li><hr style="color: inherit; background-color: gray"/></li>
%endif
- <li><hr style="color: inherit; background-color: gray"/></li>
<li><a target="galaxy_main" href="${h.url_for( controller='/history', action='list' )}">Histories</a></li>
<li><a target="galaxy_main" href="${h.url_for( controller='/dataset', action='list' )}">Datasets</a></li>
%if app.config.get_bool( 'enable_pages', False ):
1
0
15 Apr '10
details: http://www.bx.psu.edu/hg/galaxy/rev/9de680276272
changeset: 3651:9de680276272
user: Greg Von Kuster <greg(a)bx.psu.edu>
date: Wed Apr 14 14:04:03 2010 -0400
description:
Somehow missed this necessary cleanup in my initial commit for the Galaxy Community framework.
diffstat:
community_wsgi.ini.sample | 2 +-
lib/galaxy/webapps/community/model/__init__.py | 26 +++++-
lib/galaxy/webapps/community/model/mapping.py | 36 +++++++++-
lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py | 34 ++++++--
lib/galaxy/webapps/community/security/__init__.py | 37 ++++++---
templates/webapps/community/base_panels.mako | 4 +-
6 files changed, 107 insertions(+), 32 deletions(-)
diffs (291 lines):
diff -r 3d8a7da23e46 -r 9de680276272 community_wsgi.ini.sample
--- a/community_wsgi.ini.sample Wed Apr 14 13:52:35 2010 -0400
+++ b/community_wsgi.ini.sample Wed Apr 14 14:04:03 2010 -0400
@@ -17,7 +17,7 @@
log_level = DEBUG
# Database connection
-database_file = database/universe.sqlite
+#database_file = database/community.sqlite
# You may use a SQLAlchemy connection string to specify an external database instead
#database_connection = postgres:///community_test?host=/var/run/postgresql
diff -r 3d8a7da23e46 -r 9de680276272 lib/galaxy/webapps/community/model/__init__.py
--- a/lib/galaxy/webapps/community/model/__init__.py Wed Apr 14 13:52:35 2010 -0400
+++ b/lib/galaxy/webapps/community/model/__init__.py Wed Apr 14 14:04:03 2010 -0400
@@ -29,6 +29,26 @@
"""Check if 'cleartext' matches 'self.password' when hashed."""
return self.password == new_secure_hash( text_type=cleartext )
+class UserRoleAssociation( object ):
+ def __init__( self, user, role ):
+ self.user = user
+ self.role = role
+
+class Role( object ):
+ private_id = None
+ types = Bunch(
+ PRIVATE = 'private',
+ SYSTEM = 'system',
+ USER = 'user',
+ ADMIN = 'admin',
+ SHARING = 'sharing'
+ )
+ def __init__( self, name="", description="", type="system", deleted=False ):
+ self.name = name
+ self.description = description
+ self.type = type
+ self.deleted = deleted
+
class GalaxySession( object ):
def __init__( self,
id=None,
@@ -49,12 +69,6 @@
self.session_key = session_key
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 )
class Tool( object ):
def __init__( self, guid=None, name=None, description=None, category=None, version=None, user_id=None, external_filename=None ):
diff -r 3d8a7da23e46 -r 9de680276272 lib/galaxy/webapps/community/model/mapping.py
--- a/lib/galaxy/webapps/community/model/mapping.py Wed Apr 14 13:52:35 2010 -0400
+++ b/lib/galaxy/webapps/community/model/mapping.py Wed Apr 14 14:04:03 2010 -0400
@@ -44,12 +44,28 @@
Column( "create_time", DateTime, default=now ),
Column( "update_time", DateTime, default=now, onupdate=now ),
Column( "email", TrimmedString( 255 ), nullable=False ),
- Column( "username", TrimmedString( 255 ), index=True, unique=True ),
+ Column( "username", String( 255 ), index=True, unique=True, default=False ),
Column( "password", TrimmedString( 40 ), nullable=False ),
Column( "external", Boolean, default=False ),
Column( "deleted", Boolean, index=True, default=False ),
Column( "purged", Boolean, index=True, default=False ) )
+UserRoleAssociation.table = Table( "user_role_association", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
+ Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ) )
+
+Role.table = Table( "role", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "name", String( 255 ), index=True, unique=True ),
+ Column( "description", TEXT ),
+ Column( "type", String( 40 ), index=True ),
+ Column( "deleted", Boolean, index=True, default=False ) )
+
GalaxySession.table = Table( "galaxy_session", metadata,
Column( "id", Integer, primary_key=True ),
Column( "create_time", DateTime, default=now ),
@@ -121,7 +137,23 @@
properties=dict( tools=relation( Tool, order_by=desc( Tool.table.c.update_time ) ),
active_tools=relation( Tool, primaryjoin=( ( Tool.table.c.user_id == User.table.c.id ) & ( not_( Tool.table.c.deleted ) ) ), order_by=desc( Tool.table.c.update_time ) ),
galaxy_sessions=relation( GalaxySession, order_by=desc( GalaxySession.table.c.update_time ) ) ) )
-
+
+assign_mapper( context, UserRoleAssociation, UserRoleAssociation.table,
+ properties=dict(
+ user=relation( User, backref="roles" ),
+ non_private_roles=relation( User,
+ backref="non_private_roles",
+ primaryjoin=( ( User.table.c.id == UserRoleAssociation.table.c.user_id ) & ( UserRoleAssociation.table.c.role_id == Role.table.c.id ) & not_( Role.table.c.name == User.table.c.email ) ) ),
+ role=relation( Role )
+ )
+)
+
+assign_mapper( context, Role, Role.table,
+ properties=dict(
+ users=relation( UserRoleAssociation )
+ )
+)
+
assign_mapper( context, GalaxySession, GalaxySession.table,
properties=dict( user=relation( User.mapper ) ) )
diff -r 3d8a7da23e46 -r 9de680276272 lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py
--- a/lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py Wed Apr 14 13:52:35 2010 -0400
+++ b/lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py Wed Apr 14 14:04:03 2010 -0400
@@ -16,18 +16,34 @@
metadata = MetaData( migrate_engine )
-User.table = Table( "galaxy_user", metadata,
+User_table = Table( "galaxy_user", metadata,
Column( "id", Integer, primary_key=True),
Column( "create_time", DateTime, default=now ),
Column( "update_time", DateTime, default=now, onupdate=now ),
Column( "email", TrimmedString( 255 ), nullable=False ),
- Column( "username", TrimmedString( 255 ), index=True, unique=True ),
+ Column( "username", String( 255 ), index=True, unique=True, default=False ),
Column( "password", TrimmedString( 40 ), nullable=False ),
Column( "external", Boolean, default=False ),
Column( "deleted", Boolean, index=True, default=False ),
Column( "purged", Boolean, index=True, default=False ) )
-
-GalaxySession.table = Table( "galaxy_session", metadata,
+
+UserRoleAssociation_table = Table( "user_role_association", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
+ Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ) )
+
+Role_table = Table( "role", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "name", String( 255 ), index=True, unique=True ),
+ Column( "description", TEXT ),
+ Column( "type", String( 40 ), index=True ),
+ Column( "deleted", Boolean, index=True, default=False ) )
+
+GalaxySession_table = Table( "galaxy_session", metadata,
Column( "id", Integer, primary_key=True ),
Column( "create_time", DateTime, default=now ),
Column( "update_time", DateTime, default=now, onupdate=now ),
@@ -40,7 +56,7 @@
Column( "prev_session_id", Integer ) # saves a reference to the previous session so we have a way to chain them together
)
-Tool.table = Table( "tool", metadata,
+Tool_table = Table( "tool", metadata,
Column( "id", Integer, primary_key=True ),
Column( "guid", TrimmedString( 255 ), index=True, unique=True ),
Column( "create_time", DateTime, default=now ),
@@ -53,7 +69,7 @@
Column( "external_filename" , TEXT ),
Column( "deleted", Boolean, default=False ) )
-Job.table = Table( "job", metadata,
+Job_table = Table( "job", metadata,
Column( "id", Integer, primary_key=True ),
Column( "create_time", DateTime, default=now ),
Column( "update_time", DateTime, default=now, onupdate=now ),
@@ -70,14 +86,14 @@
Column( "job_runner_name", String( 255 ) ),
Column( "job_runner_external_id", String( 255 ) ) )
-Tag.table = Table( "tag", metadata,
+Tag_table = Table( "tag", metadata,
Column( "id", Integer, primary_key=True ),
Column( "type", Integer ),
Column( "parent_id", Integer, ForeignKey( "tag.id" ) ),
Column( "name", TrimmedString(255) ),
UniqueConstraint( "name" ) )
-ToolTagAssociation.table = Table( "tool_tag_association", metadata,
+ToolTagAssociation_table = Table( "tool_tag_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "tool_id", Integer, ForeignKey( "tool.id" ), index=True ),
Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ),
@@ -86,7 +102,7 @@
Column( "value", TrimmedString(255), index=True),
Column( "user_value", TrimmedString(255), index=True) )
-ToolAnnotationAssociation.table = Table( "tool_annotation_association", metadata,
+ToolAnnotationAssociation_table = Table( "tool_annotation_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "tool_id", Integer, ForeignKey( "tool.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
diff -r 3d8a7da23e46 -r 9de680276272 lib/galaxy/webapps/community/security/__init__.py
--- a/lib/galaxy/webapps/community/security/__init__.py Wed Apr 14 13:52:35 2010 -0400
+++ b/lib/galaxy/webapps/community/security/__init__.py Wed Apr 14 14:04:03 2010 -0400
@@ -18,6 +18,18 @@
class RBACAgent:
"""Class that handles galaxy community space security"""
permitted_actions = Bunch()
+ def associate_components( self, **kwd ):
+ raise 'No valid method of associating provided components: %s' % kwd
+ def associate_user_role( self, user, role ):
+ raise 'No valid method of associating a user with a role'
+ def convert_permitted_action_strings( self, permitted_action_strings ):
+ """
+ When getting permitted actions from an untrusted source like a
+ form, ensure that they match our actual permitted actions.
+ """
+ return filter( lambda x: x is not None, [ self.permitted_actions.get( action_string ) for action_string in permitted_action_strings ] )
+ def create_private_user_role( self, user ):
+ raise "Unimplemented Method"
def get_action( self, name, default=None ):
"""Get a permitted action by its dict key or action name"""
for k, v in self.permitted_actions.items():
@@ -29,16 +41,8 @@
return self.permitted_actions.__dict__.values()
def get_item_actions( self, action, item ):
raise 'No valid method of retrieving action (%s) for item %s.' % ( action, item )
- def create_private_user_role( self, user ):
- raise "Unimplemented Method"
def get_private_user_role( self, user ):
raise "Unimplemented Method"
- def convert_permitted_action_strings( self, permitted_action_strings ):
- """
- When getting permitted actions from an untrusted source like a
- form, ensure that they match our actual permitted actions.
- """
- return filter( lambda x: x is not None, [ self.permitted_actions.get( action_string ) for action_string in permitted_action_strings ] )
class CommunityRBACAgent( RBACAgent ):
def __init__( self, model, permitted_actions=None ):
@@ -49,7 +53,6 @@
def sa_session( self ):
"""Returns a SQLAlchemy session"""
return self.model.context
-
def allow_action( self, roles, action, item ):
"""
Method for checking a permission for the current user ( based on roles ) to perform a
@@ -64,9 +67,16 @@
ret_val = True
break
return ret_val
- def get_item_actions( self, action, item ):
- # item must be one of: Dataset, Library, LibraryFolder, LibraryDataset, LibraryDatasetDatasetAssociation
- return [ permission for permission in item.actions if permission.action == action.action ]
+ def associate_components( self, **kwd ):
+ if 'user' in kwd:
+ if 'role' in kwd:
+ return self.associate_user_role( kwd['user'], kwd['role'] )
+ raise 'No valid method of associating provided components: %s' % kwd
+ def associate_user_role( self, user, role ):
+ assoc = self.model.UserRoleAssociation( user, role )
+ self.sa_session.add( assoc )
+ self.sa_session.flush()
+ return assoc
def create_private_user_role( self, user ):
# Create private role
role = self.model.Role( name=user.email, description='Private Role for ' + user.email, type=self.model.Role.types.PRIVATE )
@@ -75,6 +85,9 @@
# Add user to role
self.associate_components( role=role, user=user )
return role
+ def get_item_actions( self, action, item ):
+ # item must be one of: Dataset, Library, LibraryFolder, LibraryDataset, LibraryDatasetDatasetAssociation
+ return [ permission for permission in item.actions if permission.action == action.action ]
def get_private_user_role( self, user, auto_create=False ):
role = self.sa_session.query( self.model.Role ) \
.filter( and_( self.model.Role.table.c.name == user.email,
diff -r 3d8a7da23e46 -r 9de680276272 templates/webapps/community/base_panels.mako
--- a/templates/webapps/community/base_panels.mako Wed Apr 14 13:52:35 2010 -0400
+++ b/templates/webapps/community/base_panels.mako Wed Apr 14 14:04:03 2010 -0400
@@ -60,9 +60,9 @@
%>
<div class="submenu">
<ul class="loggedout-only" style="${style1}">
- <li><a href="${h.url_for( controller='/user', action='login', webapp='community' )}">Login</a></li>
+ <li><a target="galaxy_main" href="${h.url_for( controller='/user', action='login', webapp='community' )}">Login</a></li>
%if app.config.allow_user_creation:
- <li><a href="${h.url_for( controller='/user', action='create', webapp='community' )}">Register</a></li>
+ <li><a target="galaxy_main" href="${h.url_for( controller='/user', action='create', webapp='community' )}">Register</a></li>
%endif
</ul>
<ul class="loggedin-only" style="${style2}">
1
0
15 Apr '10
details: http://www.bx.psu.edu/hg/galaxy/rev/3d8a7da23e46
changeset: 3650:3d8a7da23e46
user: Anton Nekrutenko <anton(a)bx.psu.edu>
date: Wed Apr 14 13:52:35 2010 -0400
description:
Fixed indel calling in pileup wrapper. TO DO: Write a separate wrapper for indels only.
diffstat:
tools/samtools/pileup_parser.pl | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diffs (12 lines):
diff -r cffa627364bb -r 3d8a7da23e46 tools/samtools/pileup_parser.pl
--- a/tools/samtools/pileup_parser.pl Wed Apr 14 11:27:15 2010 -0400
+++ b/tools/samtools/pileup_parser.pl Wed Apr 14 13:52:35 2010 -0400
@@ -60,7 +60,7 @@
my @qv = split //, $base_quality;
for my $base ( 0 .. @bases - 1 ) {
- if ( ord( $qv[ $base ] ) - 33 >= $quality_cutoff )
+ if ( ord( $qv[ $base ] ) - 33 >= $quality_cutoff and $bases[ $base ] ne '*')
{
++$above_qv_bases;
1
0
15 Apr '10
details: http://www.bx.psu.edu/hg/galaxy/rev/cffa627364bb
changeset: 3649:cffa627364bb
user: Greg Von Kuster <greg(a)bx.psu.edu>
date: Wed Apr 14 11:27:15 2010 -0400
description:
Apply patch from Brad Chapman that resolves the following issue - resolves ticket # 319.
GFF and GFF3 sniffer functions in datatypes/intervals.py currently require the score column to be an integer between 0 and 1000 to assign a file as valid GFF. According to the GFF3 spec, the score values are not well defined and floating point values or numbers outside that range should also be valid.
The patch relaxes this requirement to allow detection of a wider range of GFF files.
diffstat:
lib/galaxy/datatypes/interval.py | 8 ++------
1 files changed, 2 insertions(+), 6 deletions(-)
diffs (29 lines):
diff -r bb2f3963136d -r cffa627364bb lib/galaxy/datatypes/interval.py
--- a/lib/galaxy/datatypes/interval.py Wed Apr 14 11:21:00 2010 -0400
+++ b/lib/galaxy/datatypes/interval.py Wed Apr 14 11:27:15 2010 -0400
@@ -718,11 +718,9 @@
return False
if hdr[5] != '.':
try:
- score = int(hdr[5])
+ score = float( hdr[5] )
except:
return False
- if (score < 0 or score > 1000):
- return False
if hdr[6] not in data.valid_strand:
return False
return True
@@ -822,11 +820,9 @@
return False
if hdr[5] != '.':
try:
- score = int(hdr[5])
+ score = float( hdr[5] )
except:
return False
- if (score < 0 or score > 1000):
- return False
if hdr[6] not in self.valid_gff3_strand:
return False
if hdr[7] not in self.valid_gff3_phase:
1
0
15 Apr '10
details: http://www.bx.psu.edu/hg/galaxy/rev/bb2f3963136d
changeset: 3648:bb2f3963136d
user: rc
date: Wed Apr 14 11:21:00 2010 -0400
description:
lims: added email notification on request completion
diffstat:
lib/galaxy/model/__init__.py | 3 +-
lib/galaxy/model/mapping.py | 1 +
lib/galaxy/model/migrate/versions/0044_add_notify_column_to_request_table.py | 24 +++++++++
lib/galaxy/web/controllers/requests.py | 18 ++++++-
lib/galaxy/web/controllers/requests_admin.py | 26 +++++++++-
5 files changed, 69 insertions(+), 3 deletions(-)
diffs (205 lines):
diff -r f905e1415dd4 -r bb2f3963136d lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py Wed Apr 14 10:20:32 2010 -0400
+++ b/lib/galaxy/model/__init__.py Wed Apr 14 11:21:00 2010 -0400
@@ -1402,12 +1402,13 @@
REJECTED = 'Rejected',
COMPLETE = 'Complete' )
def __init__(self, name=None, desc=None, request_type=None, user=None,
- form_values=None):
+ form_values=None, notify=None):
self.name = name
self.desc = desc
self.type = request_type
self.values = form_values
self.user = user
+ self.notify = notify
self.samples_list = []
def state(self):
if self.events:
diff -r f905e1415dd4 -r bb2f3963136d lib/galaxy/model/mapping.py
--- a/lib/galaxy/model/mapping.py Wed Apr 14 10:20:32 2010 -0400
+++ b/lib/galaxy/model/mapping.py Wed Apr 14 11:21:00 2010 -0400
@@ -641,6 +641,7 @@
Column( "update_time", DateTime, default=now, onupdate=now ),
Column( "name", TrimmedString( 255 ), nullable=False ),
Column( "desc", TEXT ),
+ Column( "notify", Boolean, default=False ),
Column( "form_values_id", Integer, ForeignKey( "form_values.id" ), index=True ),
Column( "request_type_id", Integer, ForeignKey( "request_type.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
diff -r f905e1415dd4 -r bb2f3963136d lib/galaxy/model/migrate/versions/0044_add_notify_column_to_request_table.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/model/migrate/versions/0044_add_notify_column_to_request_table.py Wed Apr 14 11:21:00 2010 -0400
@@ -0,0 +1,24 @@
+"""
+Migration script to add a notify column to the request table.
+"""
+
+from sqlalchemy import *
+from migrate import *
+from migrate.changeset import *
+
+import logging
+log = logging.getLogger( __name__ )
+
+metadata = MetaData( migrate_engine )
+
+def upgrade():
+ print __doc__
+ metadata.reflect()
+
+ Request_table = Table( "request", metadata, autoload=True )
+ c = Column( "notify", Boolean, default=False )
+ c.create( Request_table )
+ assert c is Request_table.c.notify
+
+def downgrade():
+ pass
diff -r f905e1415dd4 -r bb2f3963136d lib/galaxy/web/controllers/requests.py
--- a/lib/galaxy/web/controllers/requests.py Wed Apr 14 10:20:32 2010 -0400
+++ b/lib/galaxy/web/controllers/requests.py Wed Apr 14 11:21:00 2010 -0400
@@ -209,6 +209,7 @@
request_details.append(dict(label='Type',
value=request.type.name,
helptext=''))
+
request_details.append(dict(label='State',
value=request.state(),
helptext=''))
@@ -235,6 +236,13 @@
request_details.append(dict(label=field['label'],
value=request.values.content[index],
helptext=field['helptext']+' ('+req+')'))
+ if request.notify:
+ notify = 'Yes'
+ else:
+ notify = 'No'
+ request_details.append(dict(label='Send email notification once the sequencing request is complete',
+ value=notify,
+ helptext=''))
return request_details
def __show_request(self, trans, **kwd):
params = util.Params( kwd )
@@ -707,6 +715,9 @@
util.restore_text( params.get( 'desc', '' ) )),
helptext='(Optional)'))
widgets = widgets + request_type.request_form.get_widgets( trans.user, **kwd )
+ widgets.append(dict(label='Send email notification once the sequencing request is complete',
+ widget=CheckboxField('email_notify', False),
+ helptext=''))
return trans.fill_template( '/requests/new_request.mako',
select_request_type=select_request_type,
request_type=request_type,
@@ -755,6 +766,7 @@
request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
name = util.restore_text(params.get('name', ''))
desc = util.restore_text(params.get('desc', ''))
+ notify = CheckboxField.is_checked( params.get('email_notify', '') )
# library
try:
library = trans.sa_session.query( trans.app.model.Library ).get( int( params.get( 'library_id', None ) ) )
@@ -801,7 +813,7 @@
trans.sa_session.flush()
if not request:
request = trans.app.model.Request(name, desc, request_type,
- trans.user, form_values)
+ trans.user, form_values, notify)
trans.sa_session.add( request )
trans.sa_session.flush()
trans.sa_session.refresh( request )
@@ -816,6 +828,7 @@
request.type = request_type
request.user = trans.user
request.values = form_values
+ request.notify = notify
trans.sa_session.add( request )
trans.sa_session.flush()
return request
@@ -896,6 +909,9 @@
widget=TextField('desc', 40, desc),
helptext='(Optional)'))
widgets = widgets + request.type.request_form.get_widgets( trans.user, request.values.content, **kwd )
+ widgets.append(dict(label='Send email notification once the sequencing request is complete',
+ widget=CheckboxField('email_notify', request.notify),
+ helptext=''))
return trans.fill_template( '/requests/edit_request.mako',
select_request_type=select_request_type,
request_type=request.type,
diff -r f905e1415dd4 -r bb2f3963136d lib/galaxy/web/controllers/requests_admin.py
--- a/lib/galaxy/web/controllers/requests_admin.py Wed Apr 14 10:20:32 2010 -0400
+++ b/lib/galaxy/web/controllers/requests_admin.py Wed Apr 14 11:21:00 2010 -0400
@@ -362,6 +362,9 @@
widget=TextField('desc', 40, desc),
helptext='(Optional)'))
widgets = widgets + request.type.request_form.get_widgets( request.user, request.values.content, **kwd )
+ widgets.append(dict(label='Send email notification once the sequencing request is complete',
+ widget=CheckboxField('email_notify', request.notify),
+ helptext=''))
return trans.fill_template( '/admin/requests/edit_request.mako',
select_request_type=select_request_type,
request_type=request.type,
@@ -633,6 +636,9 @@
util.restore_text( params.get( 'desc', '' ) )),
helptext='(Optional)'))
widgets = widgets + request_type.request_form.get_widgets( user, **kwd )
+ widgets.append(dict(label='Send email notification once the sequencing request is complete',
+ widget=CheckboxField('email_notify', False),
+ helptext='Email would be sent to the lab admin and the user for whom this request has been created.'))
return trans.fill_template( '/admin/requests/new_request.mako',
select_request_type=select_request_type,
request_type=request_type,
@@ -694,6 +700,7 @@
user = trans.sa_session.query( trans.app.model.User ).get( int( params.get( 'select_user', '' ) ) )
name = util.restore_text(params.get('name', ''))
desc = util.restore_text(params.get('desc', ''))
+ notify = CheckboxField.is_checked( params.get('email_notify', '') )
# fields
values = []
for index, field in enumerate(request_type.request_form.fields):
@@ -728,7 +735,7 @@
trans.sa_session.flush()
if not request:
request = trans.app.model.Request(name, desc, request_type,
- user, form_values)
+ user, form_values, notify)
trans.sa_session.add( request )
trans.sa_session.flush()
trans.sa_session.refresh( request )
@@ -745,6 +752,7 @@
request.desc = desc
request.type = request_type
request.user = user
+ request.notify = notify
request.values = form_values
trans.sa_session.add( request )
trans.sa_session.flush()
@@ -1183,6 +1191,13 @@
request_details.append(dict(label=field['label'],
value=request.values.content[index],
helptext=field['helptext']+' ('+req+')'))
+ if request.notify:
+ notify = 'Yes'
+ else:
+ notify = 'No'
+ request_details.append(dict(label='Send email notification once the sequencing request is complete',
+ value=notify,
+ helptext=''))
return request_details
def __validate_barcode(self, trans, sample, barcode):
'''
@@ -1333,6 +1348,15 @@
event = trans.app.model.RequestEvent(request, request.states.COMPLETE, comments)
trans.sa_session.add( event )
trans.sa_session.flush()
+ # now that the request is complete send the email notification to the
+ # the user
+ if request.notify:
+ mail = os.popen("%s -t" % trans.app.config.sendmail_path, 'w')
+ subject = "Galaxy Sample Tracking: '%s' sequencing request in complete." % request.name
+ body = "The '%s' sequencing request (type: %s) is now complete. Datasets from all the samples are now available for analysis or download from the respective data libraries in Galaxy." % (request.name, request.type.name)
+ email_content = "To: %s\nFrom: no-reply(a)nowhere.edu\nSubject: %s\n\n%s" % (request.user.email, subject, body)
+ mail.write( email_content )
+ x = mail.close()
def change_state(self, trans, sample):
possible_states = sample.request.type.states
curr_state = sample.current_state()
1
0
15 Apr '10
details: http://www.bx.psu.edu/hg/galaxy/rev/f905e1415dd4
changeset: 3647:f905e1415dd4
user: Greg Von Kuster <greg(a)bx.psu.edu>
date: Wed Apr 14 10:20:32 2010 -0400
description:
Framework for the Galaxy Community Space.
diffstat:
community_wsgi.ini.sample | 71 +++
lib/galaxy/tags/tag_handler.py | 5 +
lib/galaxy/webapps/community/__init__.py | 3 +
lib/galaxy/webapps/community/app.py | 34 +
lib/galaxy/webapps/community/base/controller.py | 24 +
lib/galaxy/webapps/community/buildapp.py | 198 ++++++++++
lib/galaxy/webapps/community/config.py | 153 +++++++
lib/galaxy/webapps/community/controllers/__init__.py | 1 +
lib/galaxy/webapps/community/controllers/tool_browser.py | 100 +++++
lib/galaxy/webapps/community/model/__init__.py | 187 +++++++++
lib/galaxy/webapps/community/model/mapping.py | 186 +++++++++
lib/galaxy/webapps/community/model/migrate/check.py | 105 +++++
lib/galaxy/webapps/community/model/migrate/migrate.cfg | 20 +
lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py | 101 +++++
lib/galaxy/webapps/community/security/__init__.py | 96 ++++
run_community.sh | 4 +
setup.sh | 1 +
templates/webapps/community/base_panels.mako | 102 +++++
templates/webapps/community/index.mako | 57 ++
templates/webapps/community/message.mako | 1 +
templates/webapps/community/tool/browse_tool.mako | 37 +
templates/webapps/community/tool/grid.mako | 1 +
22 files changed, 1487 insertions(+), 0 deletions(-)
diffs (1587 lines):
diff -r 4c740255b9e7 -r f905e1415dd4 community_wsgi.ini.sample
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/community_wsgi.ini.sample Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,71 @@
+# ---- HTTP Server ----------------------------------------------------------
+
+[server:main]
+
+use = egg:Paste#http
+port = 9009
+host = 127.0.0.1
+use_threadpool = true
+threadpool_workers = 10
+
+# ---- Galaxy Webapps Community Interface -------------------------------------------------
+
+[app:main]
+
+# Specifies the factory for the universe WSGI application
+paste.app_factory = galaxy.webapps.community.buildapp:app_factory
+log_level = DEBUG
+
+# Database connection
+database_file = database/universe.sqlite
+# You may use a SQLAlchemy connection string to specify an external database instead
+#database_connection = postgres:///community_test?host=/var/run/postgresql
+
+# Where dataset files are saved
+file_path = database/files
+# Temporary storage for additional datasets, this should be shared through the cluster
+new_file_path = database/tmp
+
+# Where templates are stored
+template_path = lib/galaxy/webapps/community/templates
+
+# Session support (beaker)
+use_beaker_session = True
+session_type = memory
+session_data_dir = %(here)s/database/beaker_sessions
+session_key = galaxysessions
+session_secret = changethisinproduction
+
+# Galaxy session security
+id_secret = changethisinproductiontoo
+
+# Configuration for debugging middleware
+debug = true
+use_lint = false
+
+# NEVER enable this on a public site (even test or QA)
+# use_interactive = true
+
+# Force everyone to log in (disable anonymous access)
+require_login = False
+
+# path to sendmail
+sendmail_path = /usr/sbin/sendmail
+
+# Write thread status periodically to 'heartbeat.log' (careful, uses disk space rapidly!)
+## use_heartbeat = True
+
+# Profiling middleware (cProfile based)
+## use_profile = True
+
+# Use the new iframe / javascript based layout
+use_new_layout = true
+
+# Serving static files (needed if running standalone)
+static_enabled = True
+static_cache_time = 360
+static_dir = %(here)s/static/
+static_images_dir = %(here)s/static/images
+static_favicon_dir = %(here)s/static/favicon.ico
+static_scripts_dir = %(here)s/static/scripts/
+static_style_dir = %(here)s/static/june_2007_style/blue
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/tags/tag_handler.py
--- a/lib/galaxy/tags/tag_handler.py Wed Apr 14 10:14:43 2010 -0400
+++ b/lib/galaxy/tags/tag_handler.py Wed Apr 14 10:20:32 2010 -0400
@@ -258,3 +258,8 @@
self.item_tag_assoc_info["Visualization"] = ItemTagAssocInfo( model.Visualization,
model.VisualizationTagAssociation,
model.VisualizationTagAssociation.table.c.visualization_id )
+
+class CommunityTagHandler( TagHandler ):
+ def __init__( self ):
+ from galaxy.webapps.community import model
+ TagHandler.__init__( self )
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/__init__.py Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,3 @@
+"""The Galaxy Reports application."""
+
+from galaxy.web.framework import expose, url_for
\ No newline at end of file
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/app.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/app.py Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,34 @@
+import sys, config
+import galaxy.webapps.community.model
+from galaxy.web import security
+from galaxy.tags.tag_handler import CommunityTagHandler
+
+class UniverseApplication( object ):
+ """Encapsulates the state of a Universe application"""
+ def __init__( self, **kwargs ):
+ print >> sys.stderr, "python path is: " + ", ".join( sys.path )
+ # Read config file and check for errors
+ self.config = config.Configuration( **kwargs )
+ self.config.check()
+ config.configure_logging( self.config )
+ # Determine the database url
+ if self.config.database_connection:
+ db_url = self.config.database_connection
+ else:
+ db_url = "sqlite://%s?isolation_level=IMMEDIATE" % self.config.database
+ # Initialize database / check for appropriate schema version
+ from galaxy.webapps.community.model.migrate.check import create_or_verify_database
+ create_or_verify_database( db_url, self.config.database_engine_options )
+ # Setup the database engine and ORM
+ from galaxy.webapps.community.model import mapping
+ self.model = mapping.init( self.config.file_path,
+ db_url,
+ self.config.database_engine_options )
+ # Security helper
+ self.security = security.SecurityHelper( id_secret=self.config.id_secret )
+ # Tag handler
+ self.tag_handler = CommunityTagHandler()
+ # Load security policy
+ self.security_agent = self.model.security_agent
+ def shutdown( self ):
+ pass
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/base/controller.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/base/controller.py Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,24 @@
+"""Contains functionality needed in every webapp interface"""
+import os, time, logging
+# Pieces of Galaxy to make global in every controller
+from galaxy import config, tools, web, util
+from galaxy.web import error, form, url_for
+from galaxy.webapps.community import model
+from galaxy.model.orm import *
+
+from Cheetah.Template import Template
+
+log = logging.getLogger( __name__ )
+
+class BaseController( object ):
+ """Base class for Galaxy webapp application controllers."""
+ def __init__( self, app ):
+ """Initialize an interface for application 'app'"""
+ self.app = app
+ def get_class( self, class_name ):
+ """ Returns the class object that a string denotes. Without this method, we'd have to do eval(<class_name>). """
+ if class_name == 'Tool':
+ item_class = model.Tool
+ else:
+ item_class = None
+ return item_class
\ No newline at end of file
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/buildapp.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/buildapp.py Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,198 @@
+"""
+Provides factory methods to assemble the Galaxy web application
+"""
+
+import logging, atexit
+import os, os.path, sys
+
+from inspect import isclass
+
+from paste.request import parse_formvars
+from paste.util import import_string
+from paste import httpexceptions
+from paste.deploy.converters import asbool
+import pkg_resources
+
+log = logging.getLogger( __name__ )
+
+import config
+import galaxy.webapps.community.model
+import galaxy.webapps.community.model.mapping
+import galaxy.web.framework
+
+def add_controllers( webapp, app ):
+ """
+ Search for controllers in the 'galaxy.webapps.controllers' module and add
+ them to the webapp.
+ """
+ from galaxy.webapps.community.base.controller import BaseController
+ import galaxy.webapps.community.controllers
+ controller_dir = galaxy.webapps.community.controllers.__path__[0]
+ for fname in os.listdir( controller_dir ):
+ if not fname.startswith( "_" ) and fname.endswith( ".py" ):
+ name = fname[:-3]
+ module_name = "galaxy.webapps.community.controllers." + name
+ module = __import__( module_name )
+ for comp in module_name.split( "." )[1:]:
+ module = getattr( module, comp )
+ # Look for a controller inside the modules
+ for key in dir( module ):
+ T = getattr( module, key )
+ if isclass( T ) and T is not BaseController and issubclass( T, BaseController ):
+ webapp.add_controller( name, T( app ) )
+ from galaxy.web.base.controller import BaseController
+ import galaxy.web.controllers
+ controller_dir = galaxy.web.controllers.__path__[0]
+ for fname in os.listdir( controller_dir ):
+ # TODO: fix this if we decide to use, we don't need to inspect all controllers...
+ if fname.startswith( 'user' ) and fname.endswith( ".py" ):
+ name = fname[:-3]
+ module_name = "galaxy.web.controllers." + name
+ module = __import__( module_name )
+ for comp in module_name.split( "." )[1:]:
+ module = getattr( module, comp )
+ # Look for a controller inside the modules
+ for key in dir( module ):
+ T = getattr( module, key )
+ if isclass( T ) and T is not BaseController and issubclass( T, BaseController ):
+ webapp.add_controller( name, T( app ) )
+
+def app_factory( global_conf, **kwargs ):
+ """Return a wsgi application serving the root object"""
+ # Create the Galaxy application unless passed in
+ if 'app' in kwargs:
+ app = kwargs.pop( 'app' )
+ else:
+ try:
+ from galaxy.webapps.community.app import UniverseApplication
+ app = UniverseApplication( global_conf = global_conf, **kwargs )
+ except:
+ import traceback, sys
+ traceback.print_exc()
+ sys.exit( 1 )
+ atexit.register( app.shutdown )
+ # Create the universe WSGI application
+ webapp = galaxy.web.framework.WebApplication( app, session_cookie='galaxycommunitysession' )
+ add_controllers( webapp, app )
+ # These two routes handle our simple needs at the moment
+ webapp.add_route( '/:controller/:action', action='index' )
+ webapp.add_route( '/:action', controller='tool_browser', action='index' )
+ webapp.finalize_config()
+ # Wrap the webapp in some useful middleware
+ if kwargs.get( 'middleware', True ):
+ webapp = wrap_in_middleware( webapp, global_conf, **kwargs )
+ if kwargs.get( 'static_enabled', True ):
+ webapp = wrap_in_static( webapp, global_conf, **kwargs )
+ # Close any pooled database connections before forking
+ try:
+ galaxy.webapps.community.model.mapping.metadata.engine.connection_provider._pool.dispose()
+ except:
+ pass
+ # Return
+ return webapp
+
+def wrap_in_middleware( app, global_conf, **local_conf ):
+ """Based on the configuration wrap `app` in a set of common and useful middleware."""
+ # Merge the global and local configurations
+ conf = global_conf.copy()
+ conf.update(local_conf)
+ debug = asbool( conf.get( 'debug', False ) )
+ # First put into place httpexceptions, which must be most closely
+ # wrapped around the application (it can interact poorly with
+ # other middleware):
+ app = httpexceptions.make_middleware( app, conf )
+ log.debug( "Enabling 'httpexceptions' middleware" )
+ # The recursive middleware allows for including requests in other
+ # requests or forwarding of requests, all on the server side.
+ if asbool(conf.get('use_recursive', True)):
+ from paste import recursive
+ app = recursive.RecursiveMiddleware( app, conf )
+ log.debug( "Enabling 'recursive' middleware" )
+ # Various debug middleware that can only be turned on if the debug
+ # flag is set, either because they are insecure or greatly hurt
+ # performance
+ if debug:
+ # Middleware to check for WSGI compliance
+ if asbool( conf.get( 'use_lint', True ) ):
+ from paste import lint
+ app = lint.make_middleware( app, conf )
+ log.debug( "Enabling 'lint' middleware" )
+ # Middleware to run the python profiler on each request
+ if asbool( conf.get( 'use_profile', False ) ):
+ import profile
+ app = profile.ProfileMiddleware( app, conf )
+ log.debug( "Enabling 'profile' middleware" )
+ # Middleware that intercepts print statements and shows them on the
+ # returned page
+ if asbool( conf.get( 'use_printdebug', True ) ):
+ from paste.debug import prints
+ app = prints.PrintDebugMiddleware( app, conf )
+ log.debug( "Enabling 'print debug' middleware" )
+ if debug and asbool( conf.get( 'use_interactive', False ) ):
+ # Interactive exception debugging, scary dangerous if publicly
+ # accessible, if not enabled we'll use the regular error printing
+ # middleware.
+ pkg_resources.require( "WebError" )
+ from weberror import evalexception
+ app = evalexception.EvalException( app, conf,
+ templating_formatters=build_template_error_formatters() )
+ log.debug( "Enabling 'eval exceptions' middleware" )
+ else:
+ # Not in interactive debug mode, just use the regular error middleware
+ from paste.exceptions import errormiddleware
+ app = errormiddleware.ErrorMiddleware( app, conf )
+ log.debug( "Enabling 'error' middleware" )
+ # Transaction logging (apache access.log style)
+ if asbool( conf.get( 'use_translogger', True ) ):
+ from paste.translogger import TransLogger
+ app = TransLogger( app )
+ log.debug( "Enabling 'trans logger' middleware" )
+ # Config middleware just stores the paste config along with the request,
+ # not sure we need this but useful
+ from paste.deploy.config import ConfigMiddleware
+ app = ConfigMiddleware( app, conf )
+ log.debug( "Enabling 'config' middleware" )
+ # X-Forwarded-Host handling
+ from galaxy.web.framework.middleware.xforwardedhost import XForwardedHostMiddleware
+ app = XForwardedHostMiddleware( app )
+ log.debug( "Enabling 'x-forwarded-host' middleware" )
+ return app
+
+def wrap_in_static( app, global_conf, **local_conf ):
+ from paste.urlmap import URLMap
+ from galaxy.web.framework.middleware.static import CacheableStaticURLParser as Static
+ urlmap = URLMap()
+ # Merge the global and local configurations
+ conf = global_conf.copy()
+ conf.update(local_conf)
+ # Get cache time in seconds
+ cache_time = conf.get( "static_cache_time", None )
+ if cache_time is not None:
+ cache_time = int( cache_time )
+ # Send to dynamic app by default
+ urlmap["/"] = app
+ # Define static mappings from config
+ urlmap["/static"] = Static( conf.get( "static_dir" ), cache_time )
+ urlmap["/images"] = Static( conf.get( "static_images_dir" ), cache_time )
+ urlmap["/static/scripts"] = Static( conf.get( "static_scripts_dir" ), cache_time )
+ urlmap["/static/style"] = Static( conf.get( "static_style_dir" ), cache_time )
+ urlmap["/favicon.ico"] = Static( conf.get( "static_favicon_dir" ), cache_time )
+ # URL mapper becomes the root webapp
+ return urlmap
+
+def build_template_error_formatters():
+ """
+ Build a list of template error formatters for WebError. When an error
+ occurs, WebError pass the exception to each function in this list until
+ one returns a value, which will be displayed on the error page.
+ """
+ formatters = []
+ # Formatter for mako
+ import mako.exceptions
+ def mako_html_data( exc_value ):
+ if isinstance( exc_value, ( mako.exceptions.CompileException, mako.exceptions.SyntaxException ) ):
+ return mako.exceptions.html_error_template().render( full=False, css=False )
+ if isinstance( exc_value, AttributeError ) and exc_value.args[0].startswith( "'Undefined' object has no attribute" ):
+ return mako.exceptions.html_error_template().render( full=False, css=False )
+ formatters.append( mako_html_data )
+ return formatters
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/config.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/config.py Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,153 @@
+"""
+Universe configuration builder.
+"""
+
+import sys, os
+import logging, logging.config
+from optparse import OptionParser
+import ConfigParser
+from galaxy.util import string_as_bool
+
+from galaxy import eggs
+import pkg_resources
+
+log = logging.getLogger( __name__ )
+
+def resolve_path( path, root ):
+ """If 'path' is relative make absolute by prepending 'root'"""
+ if not( os.path.isabs( path ) ):
+ path = os.path.join( root, path )
+ return path
+
+class ConfigurationError( Exception ):
+ pass
+
+class Configuration( object ):
+ def __init__( self, **kwargs ):
+ self.config_dict = kwargs
+ self.root = kwargs.get( 'root_dir', '.' )
+ # Collect the umask and primary gid from the environment
+ self.umask = os.umask( 077 ) # get the current umask
+ os.umask( self.umask ) # can't get w/o set, so set it back
+ self.gid = os.getgid() # if running under newgrp(1) we'll need to fix the group of data created on the cluster
+ # Database related configuration
+ self.database = resolve_path( kwargs.get( "database_file", "database/universe.d" ), self.root )
+ self.database_connection = kwargs.get( "database_connection", False )
+ self.database_engine_options = get_database_engine_options( kwargs )
+ self.database_create_tables = string_as_bool( kwargs.get( "database_create_tables", "True" ) )
+ # Where dataset files are stored
+ self.file_path = resolve_path( kwargs.get( "file_path", "database/files" ), self.root )
+ self.new_file_path = resolve_path( kwargs.get( "new_file_path", "database/tmp" ), self.root )
+ self.cookie_path = kwargs.get( "cookie_path", "/" )
+ self.test_conf = resolve_path( kwargs.get( "test_conf", "" ), self.root )
+ self.id_secret = kwargs.get( "id_secret", "USING THE DEFAULT IS NOT SECURE!" )
+ self.use_remote_user = string_as_bool( kwargs.get( "use_remote_user", "False" ) )
+ self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None )
+ self.remote_user_logout_href = kwargs.get( "remote_user_logout_href", None )
+ self.require_login = string_as_bool( kwargs.get( "require_login", "False" ) )
+ self.allow_user_creation = string_as_bool( kwargs.get( "allow_user_creation", "True" ) )
+ self.template_path = resolve_path( kwargs.get( "template_path", "templates" ), self.root )
+ self.template_cache = resolve_path( kwargs.get( "template_cache_path", "database/compiled_templates/community" ), self.root )
+ self.admin_users = kwargs.get( "admin_users", "" )
+ self.sendmail_path = kwargs.get('sendmail_path',"/usr/sbin/sendmail")
+ self.mailing_join_addr = kwargs.get('mailing_join_addr',"galaxy-user-join(a)bx.psu.edu")
+ self.error_email_to = kwargs.get( 'error_email_to', None )
+ self.smtp_server = kwargs.get( 'smtp_server', None )
+ self.log_actions = string_as_bool( kwargs.get( 'log_actions', 'False' ) )
+ self.brand = kwargs.get( 'brand', None )
+ self.wiki_url = kwargs.get( 'wiki_url', 'http://g2.trac.bx.psu.edu/' )
+ self.bugs_email = kwargs.get( 'bugs_email', None )
+ self.blog_url = kwargs.get( 'blog_url', None )
+ self.screencasts_url = kwargs.get( 'screencasts_url', None )
+ self.log_events = False
+ self.cloud_controller_instance = False
+ # Parse global_conf and save the parser
+ global_conf = kwargs.get( 'global_conf', None )
+ global_conf_parser = ConfigParser.ConfigParser()
+ self.global_conf_parser = global_conf_parser
+ if global_conf and "__file__" in global_conf:
+ global_conf_parser.read(global_conf['__file__'])
+ def get( self, key, default ):
+ return self.config_dict.get( key, default )
+ def get_bool( self, key, default ):
+ if key in self.config_dict:
+ return string_as_bool( self.config_dict[key] )
+ else:
+ return default
+ def check( self ):
+ # Check that required directories exist
+ for path in self.root, self.file_path, self.template_path:
+ if not os.path.isdir( path ):
+ raise ConfigurationError("Directory does not exist: %s" % path )
+ def is_admin_user( self,user ):
+ """
+ Determine if the provided user is listed in `admin_users`.
+
+ NOTE: This is temporary, admin users will likely be specified in the
+ database in the future.
+ """
+ admin_users = self.get( "admin_users", "" ).split( "," )
+ return ( user is not None and user.email in admin_users )
+
+def get_database_engine_options( kwargs ):
+ """
+ Allow options for the SQLAlchemy database engine to be passed by using
+ the prefix "database_engine_option_".
+ """
+ conversions = {
+ 'convert_unicode': string_as_bool,
+ 'pool_timeout': int,
+ 'echo': string_as_bool,
+ 'echo_pool': string_as_bool,
+ 'pool_recycle': int,
+ 'pool_size': int,
+ 'max_overflow': int,
+ 'pool_threadlocal': string_as_bool,
+ 'server_side_cursors': string_as_bool
+ }
+ prefix = "database_engine_option_"
+ prefix_len = len( prefix )
+ rval = {}
+ for key, value in kwargs.iteritems():
+ if key.startswith( prefix ):
+ key = key[prefix_len:]
+ if key in conversions:
+ value = conversions[key](value)
+ rval[ key ] = value
+ return rval
+
+def configure_logging( config ):
+ """
+ Allow some basic logging configuration to be read from the cherrpy
+ config.
+ """
+ # PasteScript will have already configured the logger if the appropriate
+ # sections were found in the config file, so we do nothing if the
+ # config has a loggers section, otherwise we do some simple setup
+ # using the 'log_*' values from the config.
+ if config.global_conf_parser.has_section( "loggers" ):
+ return
+ format = config.get( "log_format", "%(name)s %(levelname)s %(asctime)s %(message)s" )
+ level = logging._levelNames[ config.get( "log_level", "DEBUG" ) ]
+ destination = config.get( "log_destination", "stdout" )
+ log.info( "Logging at '%s' level to '%s'" % ( level, destination ) )
+ # Get root logger
+ root = logging.getLogger()
+ # Set level
+ root.setLevel( level )
+ # Turn down paste httpserver logging
+ if level <= logging.DEBUG:
+ logging.getLogger( "paste.httpserver.ThreadPool" ).setLevel( logging.WARN )
+ # Remove old handlers
+ for h in root.handlers[:]:
+ root.removeHandler(h)
+ # Create handler
+ if destination == "stdout":
+ handler = logging.StreamHandler( sys.stdout )
+ else:
+ handler = logging.FileHandler( destination )
+ # Create formatter
+ formatter = logging.Formatter( format )
+ # Hook everything up
+ handler.setFormatter( formatter )
+ root.addHandler( handler )
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/controllers/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/controllers/__init__.py Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,1 @@
+"""Galaxy community space controllers."""
\ No newline at end of file
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/controllers/tool_browser.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/controllers/tool_browser.py Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,100 @@
+import sys, os, operator, string, shutil, re, socket, urllib, time, logging
+from cgi import escape, FieldStorage
+
+from galaxy.web.base.controller import *
+from galaxy.webapps.community.base.controller import *
+from galaxy.web.framework.helpers import time_ago, iff, grids
+from galaxy.model.orm import *
+
+log = logging.getLogger( __name__ )
+
+# States for passing messages
+SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error"
+
+class ToolListGrid( grids.Grid ):
+ class NameColumn( grids.TextColumn ):
+ def get_value( self, trans, grid, tool ):
+ if tool.name:
+ return tool.name
+ return 'not set'
+ class CategoryColumn( grids.TextColumn ):
+ def get_value( self, trans, grid, tool ):
+ if tool.category:
+ return tool.category
+ return 'not set'
+
+ # Grid definition
+ title = "Tools"
+ model_class = model.Tool
+ template='/webapps/community/tool/grid.mako'
+ default_sort_key = "category"
+ columns = [
+ NameColumn( "Name",
+ key="name",
+ model_class=model.Tool,
+ attach_popup=False,
+ filterable="advanced" ),
+ CategoryColumn( "Category",
+ key="category",
+ model_class=model.Tool,
+ attach_popup=False,
+ filterable="advanced" ),
+ # Columns that are valid for filtering but are not visible.
+ grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" )
+ ]
+ columns.append( grids.MulticolFilterColumn( "Search",
+ cols_to_filter=[ columns[0], columns[1] ],
+ key="free-text-search",
+ visible=False,
+ filterable="standard" ) )
+ global_actions = [
+ grids.GridAction( "Upload tool", dict( controller='tool_browwser', action='upload' ) )
+ ]
+ operations = [
+ grids.GridOperation( "View versions", condition=( lambda item: not item.deleted ), allow_multiple=False )
+ ]
+ standard_filters = [
+ grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ),
+ grids.GridColumnFilter( "All", args=dict( deleted='All' ) )
+ ]
+ default_filter = dict( name="All", category="All", deleted="False" )
+ num_rows_per_page = 50
+ preserve_state = False
+ use_paging = True
+ def build_initial_query( self, session ):
+ return session.query( self.model_class )
+ def apply_default_filter( self, trans, query, **kwargs ):
+ return query.filter( self.model_class.deleted==False )
+
+class ToolBrowserController( BaseController ):
+
+ tool_list_grid = ToolListGrid()
+
+ @web.expose
+ def index( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ return trans.fill_template( '/webapps/community/index.mako', message=message, status=status )
+ @web.expose
+ def browse_tools( self, trans, **kwargs ):
+ if 'operation' in kwargs:
+ operation = kwargs['operation'].lower()
+ if operation == "browse":
+ return trans.response.send_redirect( web.url_for( controller='tool_browser',
+ action='browse_tool',
+ **kwargs ) )
+ # Render the list view
+ return self.tool_list_grid( trans, **kwargs )
+ @web.expose
+ def browse_tool( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ return trans.fill_template( '/webapps/community/tool/browse_tool.mako',
+ tools=tools,
+ message=message,
+ status=status )
+ @web.expose
+ def upload( self, trans, **kwargs ):
+ pass
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/model/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/model/__init__.py Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,187 @@
+"""
+Galaxy Community Space data model classes
+
+Naming: try to use class names that have a distinct plural form so that
+the relationship cardinalities are obvious (e.g. prefer Dataset to Data)
+"""
+import os.path, os, errno, sys, codecs, operator, tempfile, logging
+from galaxy.util.bunch import Bunch
+from galaxy import util
+from galaxy.util.hash_util import *
+from galaxy.web.form_builder import *
+log = logging.getLogger( __name__ )
+from sqlalchemy.orm import object_session
+
+class User( object ):
+ def __init__( self, email=None, password=None ):
+ self.email = email
+ self.password = password
+ self.external = False
+ self.deleted = False
+ self.purged = False
+ self.username = None
+ # Relationships
+ self.tools = []
+ def set_password_cleartext( self, cleartext ):
+ """Set 'self.password' to the digest of 'cleartext'."""
+ self.password = new_secure_hash( text_type=cleartext )
+ def check_password( self, cleartext ):
+ """Check if 'cleartext' matches 'self.password' when hashed."""
+ return self.password == new_secure_hash( text_type=cleartext )
+
+class GalaxySession( object ):
+ def __init__( self,
+ id=None,
+ user=None,
+ remote_host=None,
+ remote_addr=None,
+ referer=None,
+ current_history=None,
+ session_key=None,
+ is_valid=False,
+ prev_session_id=None ):
+ self.id = id
+ self.user = user
+ self.remote_host = remote_host
+ self.remote_addr = remote_addr
+ self.referer = referer
+ self.current_history = current_history
+ self.session_key = session_key
+ 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 )
+
+class Tool( object ):
+ def __init__( self, guid=None, name=None, description=None, category=None, version=None, user_id=None, external_filename=None ):
+ self.guid = guid
+ self.name = name or "Unnamed tool"
+ self.description = description
+ self.category = category
+ self.version = version or "1.0.0"
+ self.user_id = user_id
+ self.external_filename = external_filename
+
+class Job( object ):
+ """
+ A job represents a request to run a tool given input datasets, tool
+ parameters, and output datasets.
+ """
+ states = Bunch( NEW = 'new',
+ UPLOAD = 'upload',
+ WAITING = 'waiting',
+ QUEUED = 'queued',
+ RUNNING = 'running',
+ OK = 'ok',
+ ERROR = 'error',
+ DELETED = 'deleted' )
+ def __init__( self ):
+ self.session_id = None
+ self.tool_id = None
+ self.tool_version = None
+ self.command_line = None
+ self.param_filename = None
+ self.parameters = []
+ self.input_datasets = []
+ self.output_datasets = []
+ self.output_library_datasets = []
+ self.state = Job.states.NEW
+ self.info = None
+ self.job_runner_name = None
+ self.job_runner_external_id = None
+ def add_parameter( self, name, value ):
+ self.parameters.append( JobParameter( name, value ) )
+ def add_input_dataset( self, name, dataset ):
+ self.input_datasets.append( JobToInputDatasetAssociation( name, dataset ) )
+ def add_output_dataset( self, name, dataset ):
+ self.output_datasets.append( JobToOutputDatasetAssociation( name, dataset ) )
+ def add_output_library_dataset( self, name, dataset ):
+ self.output_library_datasets.append( JobToOutputLibraryDatasetAssociation( name, dataset ) )
+ def set_state( self, state ):
+ self.state = state
+ # For historical reasons state propogates down to datasets
+ for da in self.output_datasets:
+ da.dataset.state = state
+ def get_param_values( self, app ):
+ """
+ Read encoded parameter values from the database and turn back into a
+ dict of tool parameter values.
+ """
+ param_dict = dict( [ ( p.name, p.value ) for p in self.parameters ] )
+ tool = app.toolbox.tools_by_id[self.tool_id]
+ param_dict = tool.params_from_strings( param_dict, app )
+ return param_dict
+ def check_if_output_datasets_deleted( self ):
+ """
+ Return true if all of the output datasets associated with this job are
+ in the deleted state
+ """
+ for dataset_assoc in self.output_datasets:
+ dataset = dataset_assoc.dataset
+ # only the originator of the job can delete a dataset to cause
+ # cancellation of the job, no need to loop through history_associations
+ if not dataset.deleted:
+ return False
+ return True
+ def mark_deleted( self ):
+ """
+ Mark this job as deleted, and mark any output datasets as discarded.
+ """
+ self.state = Job.states.DELETED
+ self.info = "Job output deleted by user before job completed."
+ for dataset_assoc in self.output_datasets:
+ dataset = dataset_assoc.dataset
+ dataset.deleted = True
+ dataset.state = dataset.states.DISCARDED
+ for dataset in dataset.dataset.history_associations:
+ # propagate info across shared datasets
+ dataset.deleted = True
+ dataset.blurb = 'deleted'
+ dataset.peek = 'Job deleted'
+ dataset.info = 'Job output deleted by user before job completed'
+
+class Tag ( object ):
+ def __init__( self, id=None, type=None, parent_id=None, name=None ):
+ self.id = id
+ self.type = type
+ self.parent_id = parent_id
+ self.name = name
+ def __str__ ( self ):
+ return "Tag(id=%s, type=%i, parent_id=%s, name=%s)" % ( self.id, self.type, self.parent_id, self.name )
+
+class ItemTagAssociation ( object ):
+ def __init__( self, id=None, user=None, item_id=None, tag_id=None, user_tname=None, value=None ):
+ self.id = id
+ self.user = user
+ self.item_id = item_id
+ self.tag_id = tag_id
+ self.user_tname = user_tname
+ self.value = None
+ self.user_value = None
+
+class ToolTagAssociation ( ItemTagAssociation ):
+ pass
+
+class ToolAnnotationAssociation( object ):
+ pass
+
+## ---- Utility methods -------------------------------------------------------
+
+def directory_hash_id( id ):
+ s = str( id )
+ l = len( s )
+ # Shortcut -- ids 0-999 go under ../000/
+ if l < 4:
+ return [ "000" ]
+ # Pad with zeros until a multiple of three
+ padded = ( ( 3 - len( s ) % 3 ) * "0" ) + s
+ # Drop the last three digits -- 1000 files per directory
+ padded = padded[:-3]
+ # Break into chunks of three
+ return [ padded[i*3:(i+1)*3] for i in range( len( padded ) // 3 ) ]
+
+
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/model/mapping.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/model/mapping.py Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,186 @@
+"""
+Details of how the data model objects are mapped onto the relational database
+are encapsulated here.
+"""
+import logging
+log = logging.getLogger( __name__ )
+
+import sys
+import datetime
+
+from galaxy.webapps.community.model import *
+from galaxy.model.orm import *
+from galaxy.model.orm.ext.assignmapper import *
+from galaxy.model.custom_types import *
+from galaxy.util.bunch import Bunch
+from galaxy.webapps.community.security import CommunityRBACAgent
+from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.ext.associationproxy import association_proxy
+
+metadata = MetaData()
+context = Session = scoped_session( sessionmaker( autoflush=False, autocommit=True ) )
+
+# For backward compatibility with "context.current"
+context.current = Session
+
+dialect_to_egg = {
+ "sqlite" : "pysqlite>=2",
+ "postgres" : "psycopg2",
+ "mysql" : "MySQL_python"
+}
+
+# NOTE REGARDING TIMESTAMPS:
+# It is currently difficult to have the timestamps calculated by the
+# database in a portable way, so we're doing it in the client. This
+# also saves us from needing to postfetch on postgres. HOWEVER: it
+# relies on the client's clock being set correctly, so if clustering
+# web servers, use a time server to ensure synchronization
+
+# Return the current time in UTC without any timezone information
+now = datetime.datetime.utcnow
+
+User.table = Table( "galaxy_user", metadata,
+ Column( "id", Integer, primary_key=True),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "email", TrimmedString( 255 ), nullable=False ),
+ Column( "username", TrimmedString( 255 ), index=True, unique=True ),
+ Column( "password", TrimmedString( 40 ), nullable=False ),
+ Column( "external", Boolean, default=False ),
+ Column( "deleted", Boolean, index=True, default=False ),
+ Column( "purged", Boolean, index=True, default=False ) )
+
+GalaxySession.table = Table( "galaxy_session", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=True ),
+ Column( "remote_host", String( 255 ) ),
+ Column( "remote_addr", String( 255 ) ),
+ Column( "referer", TEXT ),
+ Column( "session_key", TrimmedString( 255 ), index=True, unique=True ), # unique 128 bit random number coerced to a string
+ Column( "is_valid", Boolean, default=False ),
+ Column( "prev_session_id", Integer ) # saves a reference to the previous session so we have a way to chain them together
+ )
+
+Tool.table = Table( "tool", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "guid", TrimmedString( 255 ), index=True, unique=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "name", TrimmedString( 255 ), index=True, unique=True ),
+ Column( "description" , TEXT ),
+ Column( "category", TrimmedString( 255 ), index=True ),
+ Column( "version", TrimmedString( 255 ) ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
+ Column( "external_filename" , TEXT ),
+ Column( "deleted", Boolean, default=False ) )
+
+Job.table = Table( "job", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "tool_id", Integer, ForeignKey( "tool.id" ), index=True ),
+ Column( "state", String( 64 ), index=True ),
+ Column( "info", TrimmedString( 255 ) ),
+ Column( "command_line", TEXT ),
+ Column( "param_filename", String( 1024 ) ),
+ Column( "runner_name", String( 255 ) ),
+ Column( "stdout", TEXT ),
+ Column( "stderr", TEXT ),
+ Column( "traceback", TEXT ),
+ Column( "session_id", Integer, ForeignKey( "galaxy_session.id" ), index=True, nullable=True ),
+ Column( "job_runner_name", String( 255 ) ),
+ Column( "job_runner_external_id", String( 255 ) ) )
+
+Tag.table = Table( "tag", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "type", Integer ),
+ Column( "parent_id", Integer, ForeignKey( "tag.id" ) ),
+ Column( "name", TrimmedString(255) ),
+ UniqueConstraint( "name" ) )
+
+ToolTagAssociation.table = Table( "tool_tag_association", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "tool_id", Integer, ForeignKey( "tool.id" ), index=True ),
+ Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
+ Column( "user_tname", TrimmedString(255), index=True),
+ Column( "value", TrimmedString(255), index=True),
+ Column( "user_value", TrimmedString(255), index=True) )
+
+ToolAnnotationAssociation.table = Table( "tool_annotation_association", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "tool_id", Integer, ForeignKey( "tool.id" ), index=True ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
+ Column( "annotation", TEXT, index=True) )
+
+# With the tables defined we can define the mappers and setup the
+# relationships between the model objects.
+assign_mapper( context, User, User.table,
+ properties=dict( tools=relation( Tool, order_by=desc( Tool.table.c.update_time ) ),
+ active_tools=relation( Tool, primaryjoin=( ( Tool.table.c.user_id == User.table.c.id ) & ( not_( Tool.table.c.deleted ) ) ), order_by=desc( Tool.table.c.update_time ) ),
+ galaxy_sessions=relation( GalaxySession, order_by=desc( GalaxySession.table.c.update_time ) ) ) )
+
+assign_mapper( context, GalaxySession, GalaxySession.table,
+ properties=dict( user=relation( User.mapper ) ) )
+
+assign_mapper( context, Job, Job.table,
+ properties=dict( galaxy_session=relation( GalaxySession ),
+ tool=relation( Tool ) ) )
+
+assign_mapper( context, Tag, Tag.table,
+ properties=dict( children=relation(Tag, backref=backref( 'parent', remote_side=[Tag.table.c.id] ) ) ) )
+
+assign_mapper( context, ToolTagAssociation, ToolTagAssociation.table,
+ properties=dict( tag=relation(Tag, backref="tagged_tools"), user=relation( User ) ) )
+
+assign_mapper( context, ToolAnnotationAssociation, ToolAnnotationAssociation.table,
+ properties=dict( tool=relation( Tool ), user=relation( User ) ) )
+
+assign_mapper( context, Tool, Tool.table,
+ properties = dict( user=relation( User.mapper ) ) )
+
+def guess_dialect_for_url( url ):
+ return (url.split(':', 1))[0]
+
+def load_egg_for_url( url ):
+ # Load the appropriate db module
+ dialect = guess_dialect_for_url( url )
+ try:
+ egg = dialect_to_egg[dialect]
+ try:
+ pkg_resources.require( egg )
+ log.debug( "%s egg successfully loaded for %s dialect" % ( egg, dialect ) )
+ except:
+ # If the module's in the path elsewhere (i.e. non-egg), it'll still load.
+ log.warning( "%s egg not found, but an attempt will be made to use %s anyway" % ( egg, dialect ) )
+ except KeyError:
+ # Let this go, it could possibly work with db's we don't support
+ log.error( "database_connection contains an unknown SQLAlchemy database dialect: %s" % dialect )
+
+def init( file_path, url, engine_options={}, create_tables=False ):
+ """Connect mappings to the database"""
+ log.debug("###In init, file_path: %s" % str( file_path ))
+ # Connect dataset to the file path
+ Tool.file_path = file_path
+ # Load the appropriate db module
+ load_egg_for_url( url )
+ # Create the database engine
+ engine = create_engine( url, **engine_options )
+ # Connect the metadata to the database.
+ metadata.bind = engine
+ # Clear any existing contextual sessions and reconfigure
+ Session.remove()
+ Session.configure( bind=engine )
+ # Create tables if needed
+ if create_tables:
+ metadata.create_all()
+ # Pack everything into a bunch
+ result = Bunch( **globals() )
+ result.engine = engine
+ result.session = Session
+ result.create_tables = create_tables
+ #load local galaxy security policy
+ result.security_agent = CommunityRBACAgent( result )
+ return result
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/model/migrate/check.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/model/migrate/check.py Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,105 @@
+import sys, os.path, logging
+
+from galaxy import eggs
+
+import pkg_resources
+pkg_resources.require( "sqlalchemy-migrate" )
+
+from migrate.versioning import repository, schema
+from sqlalchemy import *
+from sqlalchemy.exc import NoSuchTableError
+
+log = logging.getLogger( __name__ )
+
+# path relative to galaxy
+migrate_repository_directory = os.path.dirname( __file__ ).replace( os.getcwd() + os.path.sep, '', 1 )
+migrate_repository = repository.Repository( migrate_repository_directory )
+dialect_to_egg = {
+ "sqlite" : "pysqlite>=2",
+ "postgres" : "psycopg2",
+ "mysql" : "MySQL_python"
+}
+
+def create_or_verify_database( url, engine_options={} ):
+ """
+ Check that the database is use-able, possibly creating it if empty (this is
+ the only time we automatically create tables, otherwise we force the
+ user to do it using the management script so they can create backups).
+
+ 1) Empty database --> initialize with latest version and return
+ 2) Database older than migration support --> fail and require manual update
+ 3) Database at state where migrate support introduced --> add version control information but make no changes (might still require manual update)
+ 4) Database versioned but out of date --> fail with informative message, user must run "sh manage_db.sh upgrade"
+
+ """
+ dialect = ( url.split( ':', 1 ) )[0]
+ try:
+ egg = dialect_to_egg[dialect]
+ try:
+ pkg_resources.require( egg )
+ log.debug( "%s egg successfully loaded for %s dialect" % ( egg, dialect ) )
+ except:
+ # If the module is in the path elsewhere (i.e. non-egg), it'll still load.
+ log.warning( "%s egg not found, but an attempt will be made to use %s anyway" % ( egg, dialect ) )
+ except KeyError:
+ # Let this go, it could possibly work with db's we don't support
+ log.error( "database_connection contains an unknown SQLAlchemy database dialect: %s" % dialect )
+ # Create engine and metadata
+ engine = create_engine( url, **engine_options )
+ meta = MetaData( bind=engine )
+ # Try to load dataset table
+ try:
+ galaxy_user_table = Table( "galaxy_user", meta, autoload=True )
+ except NoSuchTableError:
+ # No 'galaxy_user' table means a completely uninitialized database, which
+ # is fine, init the database in a versioned state
+ log.info( "No database, initializing" )
+ # Database might or might not be versioned
+ try:
+ # Declare the database to be under a repository's version control
+ db_schema = schema.ControlledSchema.create( engine, migrate_repository )
+ except:
+ # The database is already under version control
+ db_schema = schema.ControlledSchema( engine, migrate_repository )
+ # Apply all scripts to get to current version
+ migrate_to_current_version( engine, db_schema )
+ return
+ try:
+ version_table = Table( "migrate_version", meta, autoload=True )
+ except NoSuchTableError:
+ # The database exists but is not yet under migrate version control, so init with version 1
+ log.info( "Adding version control to existing database" )
+ try:
+ metadata_file_table = Table( "metadata_file", meta, autoload=True )
+ schema.ControlledSchema.create( engine, migrate_repository, version=2 )
+ except NoSuchTableError:
+ schema.ControlledSchema.create( engine, migrate_repository, version=1 )
+ # Verify that the code and the DB are in sync
+ db_schema = schema.ControlledSchema( engine, migrate_repository )
+ if migrate_repository.versions.latest != db_schema.version:
+ raise Exception( "Your database has version '%d' but this code expects version '%d'. Please backup your database and then migrate the schema by running 'sh manage_db.sh upgrade'."
+ % ( db_schema.version, migrate_repository.versions.latest ) )
+ else:
+ log.info( "At database version %d" % db_schema.version )
+
+def migrate_to_current_version( engine, schema ):
+ # Changes to get to current version
+ changeset = schema.changeset( None )
+ for ver, change in changeset:
+ nextver = ver + changeset.step
+ log.info( 'Migrating %s -> %s... ' % ( ver, nextver ) )
+ old_stdout = sys.stdout
+ class FakeStdout( object ):
+ def __init__( self ):
+ self.buffer = []
+ def write( self, s ):
+ self.buffer.append( s )
+ def flush( self ):
+ pass
+ sys.stdout = FakeStdout()
+ try:
+ schema.runchange( ver, change, changeset.step )
+ finally:
+ for message in "".join( sys.stdout.buffer ).split( "\n" ):
+ log.info( message )
+ sys.stdout = old_stdout
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/model/migrate/migrate.cfg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/model/migrate/migrate.cfg Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,20 @@
+[db_settings]
+# Used to identify which repository this database is versioned under.
+# You can use the name of your project.
+repository_id=Galaxy
+
+# The name of the database table used to track the schema version.
+# This name shouldn't already be used by your project.
+# If this is changed once a database is under version control, you'll need to
+# change the table name in each database too.
+version_table=migrate_version
+
+# When committing a change script, Migrate will attempt to generate the
+# sql for all supported databases; normally, if one of them fails - probably
+# because you don't have that database installed - it is ignored and the
+# commit continues, perhaps ending successfully.
+# Databases in this list MUST compile successfully during a commit, or the
+# entire commit will fail. List the databases your application will actually
+# be using to ensure your updates to that database work properly.
+# This must be a list; example: ['postgres','sqlite']
+required_dbs=[]
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,101 @@
+"""
+Migration script to create initial tables.
+"""
+
+from sqlalchemy import *
+from migrate import *
+
+import datetime
+now = datetime.datetime.utcnow
+
+# Need our custom types, but don't import anything else from model
+from galaxy.model.custom_types import *
+
+import logging
+log = logging.getLogger( __name__ )
+
+metadata = MetaData( migrate_engine )
+
+User.table = Table( "galaxy_user", metadata,
+ Column( "id", Integer, primary_key=True),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "email", TrimmedString( 255 ), nullable=False ),
+ Column( "username", TrimmedString( 255 ), index=True, unique=True ),
+ Column( "password", TrimmedString( 40 ), nullable=False ),
+ Column( "external", Boolean, default=False ),
+ Column( "deleted", Boolean, index=True, default=False ),
+ Column( "purged", Boolean, index=True, default=False ) )
+
+GalaxySession.table = Table( "galaxy_session", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=True ),
+ Column( "remote_host", String( 255 ) ),
+ Column( "remote_addr", String( 255 ) ),
+ Column( "referer", TEXT ),
+ Column( "session_key", TrimmedString( 255 ), index=True, unique=True ), # unique 128 bit random number coerced to a string
+ Column( "is_valid", Boolean, default=False ),
+ Column( "prev_session_id", Integer ) # saves a reference to the previous session so we have a way to chain them together
+ )
+
+Tool.table = Table( "tool", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "guid", TrimmedString( 255 ), index=True, unique=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "name", TrimmedString( 255 ), index=True, unique=True ),
+ Column( "description" , TEXT ),
+ Column( "category", TrimmedString( 255 ), index=True ),
+ Column( "version", TrimmedString( 255 ) ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
+ Column( "external_filename" , TEXT ),
+ Column( "deleted", Boolean, default=False ) )
+
+Job.table = Table( "job", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "tool_id", Integer, ForeignKey( "tool.id" ), index=True ),
+ Column( "state", String( 64 ), index=True ),
+ Column( "info", TrimmedString( 255 ) ),
+ Column( "command_line", TEXT ),
+ Column( "param_filename", String( 1024 ) ),
+ Column( "runner_name", String( 255 ) ),
+ Column( "stdout", TEXT ),
+ Column( "stderr", TEXT ),
+ Column( "traceback", TEXT ),
+ Column( "session_id", Integer, ForeignKey( "galaxy_session.id" ), index=True, nullable=True ),
+ Column( "job_runner_name", String( 255 ) ),
+ Column( "job_runner_external_id", String( 255 ) ) )
+
+Tag.table = Table( "tag", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "type", Integer ),
+ Column( "parent_id", Integer, ForeignKey( "tag.id" ) ),
+ Column( "name", TrimmedString(255) ),
+ UniqueConstraint( "name" ) )
+
+ToolTagAssociation.table = Table( "tool_tag_association", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "tool_id", Integer, ForeignKey( "tool.id" ), index=True ),
+ Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
+ Column( "user_tname", TrimmedString(255), index=True),
+ Column( "value", TrimmedString(255), index=True),
+ Column( "user_value", TrimmedString(255), index=True) )
+
+ToolAnnotationAssociation.table = Table( "tool_annotation_association", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "tool_id", Integer, ForeignKey( "tool.id" ), index=True ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
+ Column( "annotation", TEXT, index=True) )
+
+def upgrade():
+ print __doc__
+ metadata.create_all()
+
+def downgrade():
+ # Operations to reverse the above upgrade go here.
+ pass
diff -r 4c740255b9e7 -r f905e1415dd4 lib/galaxy/webapps/community/security/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/webapps/community/security/__init__.py Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,96 @@
+"""
+Galaxy Community Space Security
+"""
+import logging, socket, operator
+from datetime import datetime, timedelta
+from galaxy.util.bunch import Bunch
+from galaxy.util import listify
+from galaxy.model.orm import *
+
+log = logging.getLogger(__name__)
+
+class Action( object ):
+ def __init__( self, action, description, model ):
+ self.action = action
+ self.description = description
+ self.model = model
+
+class RBACAgent:
+ """Class that handles galaxy community space security"""
+ permitted_actions = Bunch()
+ def get_action( self, name, default=None ):
+ """Get a permitted action by its dict key or action name"""
+ for k, v in self.permitted_actions.items():
+ if k == name or v.action == name:
+ return v
+ return default
+ def get_actions( self ):
+ """Get all permitted actions as a list of Action objects"""
+ return self.permitted_actions.__dict__.values()
+ def get_item_actions( self, action, item ):
+ raise 'No valid method of retrieving action (%s) for item %s.' % ( action, item )
+ def create_private_user_role( self, user ):
+ raise "Unimplemented Method"
+ def get_private_user_role( self, user ):
+ raise "Unimplemented Method"
+ def convert_permitted_action_strings( self, permitted_action_strings ):
+ """
+ When getting permitted actions from an untrusted source like a
+ form, ensure that they match our actual permitted actions.
+ """
+ return filter( lambda x: x is not None, [ self.permitted_actions.get( action_string ) for action_string in permitted_action_strings ] )
+
+class CommunityRBACAgent( RBACAgent ):
+ def __init__( self, model, permitted_actions=None ):
+ self.model = model
+ if permitted_actions:
+ self.permitted_actions = permitted_actions
+ @property
+ def sa_session( self ):
+ """Returns a SQLAlchemy session"""
+ return self.model.context
+
+ def allow_action( self, roles, action, item ):
+ """
+ Method for checking a permission for the current user ( based on roles ) to perform a
+ specific action on an item
+ """
+ item_actions = self.get_item_actions( action, item )
+ if not item_actions:
+ return action.model == 'restrict'
+ ret_val = False
+ for item_action in item_actions:
+ if item_action.role in roles:
+ ret_val = True
+ break
+ return ret_val
+ def get_item_actions( self, action, item ):
+ # item must be one of: Dataset, Library, LibraryFolder, LibraryDataset, LibraryDatasetDatasetAssociation
+ return [ permission for permission in item.actions if permission.action == action.action ]
+ def create_private_user_role( self, user ):
+ # Create private role
+ role = self.model.Role( name=user.email, description='Private Role for ' + user.email, type=self.model.Role.types.PRIVATE )
+ self.sa_session.add( role )
+ self.sa_session.flush()
+ # Add user to role
+ self.associate_components( role=role, user=user )
+ return role
+ def get_private_user_role( self, user, auto_create=False ):
+ role = self.sa_session.query( self.model.Role ) \
+ .filter( and_( self.model.Role.table.c.name == user.email,
+ self.model.Role.table.c.type == self.model.Role.types.PRIVATE ) ) \
+ .first()
+ if not role:
+ if auto_create:
+ return self.create_private_user_role( user )
+ else:
+ return None
+ return role
+
+def get_permitted_actions( filter=None ):
+ '''Utility method to return a subset of RBACAgent's permitted actions'''
+ if filter is None:
+ return RBACAgent.permitted_actions
+ tmp_bunch = Bunch()
+ [ tmp_bunch.__dict__.__setitem__(k, v) for k, v in RBACAgent.permitted_actions.items() if k.startswith( filter ) ]
+ return tmp_bunch
diff -r 4c740255b9e7 -r f905e1415dd4 run_community.sh
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/run_community.sh Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+cd `dirname $0`
+python ./scripts/paster.py serve community_wsgi.ini --pid-file=community_webapp.pid --log-file=community_webapp.log $@
diff -r 4c740255b9e7 -r f905e1415dd4 setup.sh
--- a/setup.sh Wed Apr 14 10:14:43 2010 -0400
+++ b/setup.sh Wed Apr 14 10:20:32 2010 -0400
@@ -31,6 +31,7 @@
DIRS="
database
database/files
+database/tools
database/tmp
database/compiled_templates
database/job_working_directory
diff -r 4c740255b9e7 -r f905e1415dd4 templates/webapps/community/base_panels.mako
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/webapps/community/base_panels.mako Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,102 @@
+<%inherit file="/base_panels.mako"/>
+
+## Default title
+<%def name="title()">Galaxy Community Space</%def>
+
+## Masthead
+<%def name="masthead()">
+
+ ## Tab area, fills entire width
+ <div style="position: absolute; top: 0; left: 0; width: 100%; text-align: center">
+
+ <table class="tab-group" border="0" cellspacing="0" style="margin: auto;">
+ <tr>
+
+ <%def name="tab( id, display, href, target='_parent', visible=True, extra_class='' )">
+ <%
+ cls = "tab"
+ if extra_class:
+ cls += " " + extra_class
+ if self.active_view == id:
+ cls += " active"
+ style = ""
+ if not visible:
+ style = "display: none;"
+ %>
+ <td class="${cls}" style="${style}"><a target="${target}" href="${href}">${display}</a></td>
+ </%def>
+
+ ${tab( "admin", "Admin", h.url_for( controller='/webapps/community/admin', action='index' ), extra_class="admin-only", visible=( trans.user and app.config.is_admin_user( trans.user ) ) )}
+
+ <td class="tab">
+ <a>Help</a>
+ <div class="submenu">
+ <ul>
+ <li><a href="${app.config.get( "bugs_email", "mailto:galaxy-bugs@bx.psu.edu" )}">Email comments, bug reports, or suggestions</a></li>
+ <li><a target="_blank" href="${app.config.get( "wiki_url", "http://bitbucket.org/galaxy/galaxy-central/wiki" )}">Galaxy Wiki</a></li>
+ <li><a target="_blank" href="${app.config.get( "screencasts_url", "http://galaxycast.org" )}">Video tutorials (screencasts)</a></li>
+ <li><a target="_blank" href="${app.config.get( "citation_url", "http://bitbucket.org/galaxy/galaxy-central/wiki/Citations" )}">How to Cite Galaxy</a></li>
+ </ul>
+ </div>
+ </td>
+
+ ## User tab.
+ <%
+ cls = "tab"
+ if self.active_view == 'user':
+ cls += " active"
+ %>
+ <td class="${cls}">
+ <a>User</a>
+ <%
+ if trans.user:
+ user_email = trans.user.email
+ style1 = "display: none;"
+ style2 = "";
+ else:
+ user_email = ""
+ style1 = ""
+ style2 = "display: none;"
+ %>
+ <div class="submenu">
+ <ul class="loggedout-only" style="${style1}">
+ <li><a href="${h.url_for( controller='/user', action='login', webapp='community' )}">Login</a></li>
+ %if app.config.allow_user_creation:
+ <li><a href="${h.url_for( controller='/user', action='create', webapp='community' )}">Register</a></li>
+ %endif
+ </ul>
+ <ul class="loggedin-only" style="${style2}">
+ %if app.config.use_remote_user:
+ %if app.config.remote_user_logout_href:
+ <li><a href="${app.config.remote_user_logout_href}" target="_top">Logout</a></li>
+ %endif
+ %else:
+ <li>Logged in as <span id="user-email">${user_email}</span></li>
+ <li><a target="galaxy_main" href="${h.url_for( controller='/user', action='index', webapp='community' )}">Preferences</a></li>
+ <%
+ if app.config.require_login:
+ logout_url = h.url_for( controller='/root', action='index', webapp='community', m_c='user', m_a='logout' )
+ else:
+ logout_url = h.url_for( controller='/user', action='logout', webapp='community' )
+ %>
+ <li><a target="_top" href="${logout_url}">Logout</a></li>
+ %endif
+ </ul>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ ## Logo, layered over tabs to be clickable
+ <div class="title" style="position: absolute; top: 0; left: 0;">
+ <a href="${app.config.get( 'logo_url', '/' )}">
+ <img border="0" src="${h.url_for('/static/images/galaxyIcon_noText.png')}" style="width: 26px; vertical-align: top;">
+ Galaxy
+ %if app.config.brand:
+ <span class='brand'>/ ${app.config.brand}</span>
+ %endif
+ </a>
+ </div>
+
+</%def>
diff -r 4c740255b9e7 -r f905e1415dd4 templates/webapps/community/index.mako
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/webapps/community/index.mako Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,57 @@
+<%inherit file="/webapps/community/base_panels.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+<%def name="init()">
+ <%
+ self.has_left_panel=True
+ self.has_right_panel=False
+ self.active_view="tools"
+ %>
+ %if trans.app.config.require_login and not trans.user:
+ <script type="text/javascript">
+ if ( window != top ) {
+ top.location.href = location.href;
+ }
+ </script>
+ %endif
+</%def>
+
+<%def name="left_panel()">
+ <div class="unified-panel-header" unselectable="on">
+ <div class='unified-panel-header-inner'>Community</div>
+ </div>
+ <div class="page-container" style="padding: 10px;">
+ <div class="toolMenu">
+ <div class="toolSectionList">
+ <div class="toolSectionPad"></div>
+ <div class="toolSectionTitle">
+ <span>Tools</span>
+ </div>
+ <div class="toolSectionBody">
+ <div class="toolSectionBg">
+ <div class="toolTitle"><a href="${h.url_for( controller='tool_browser', action='browse_tools' )}" target="galaxy_main">Browse tools</a></div>
+ </div>
+ </div>
+ <div class="toolSectionPad"></div>
+ <div class="toolSectionTitle">
+ <span>Forum</span>
+ </div>
+ <div class="toolSectionBody">
+ <div class="toolSectionBg">
+ <div class="toolTitle"><a href="${h.url_for( controller='forum', action='browse_forums' )}" target="galaxy_main">Forums</a></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</%def>
+
+<%def name="center_panel()">
+ <%
+ if trans.app.config.require_login and not trans.user:
+ center_url = h.url_for( controller='user', action='login', message=message, status=status )
+ else:
+ center_url = h.url_for( controller='tool_browser', action='browse_tools', message=message, status=status )
+ %>
+ <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${center_url}"> </iframe>
+</%def>
diff -r 4c740255b9e7 -r f905e1415dd4 templates/webapps/community/message.mako
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/webapps/community/message.mako Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,1 @@
+<%inherit file="/message.mako"/>
diff -r 4c740255b9e7 -r f905e1415dd4 templates/webapps/community/tool/browse_tool.mako
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/webapps/community/tool/browse_tool.mako Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,37 @@
+<%namespace file="/message.mako" import="render_msg" />
+
+<%!
+ def inherit(context):
+ if context.get('use_panels'):
+ return '/webapps/community/base_panels.mako'
+ else:
+ return '/base.mako'
+%>
+<%inherit file="${inherit(context)}"/>
+
+<%def name="title()">Browse Tool</%def>
+
+<h2>Galaxy Tool</h2>
+
+%if message:
+ ${render_msg( message, status )}
+%endif
+
+%if not tools:
+ There are no tools
+%else:
+ <table class="grid">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Description</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="formRow id="toolRow">
+ <td><a href="${h.url_for( controller='tool_browser', action='browse', id=trans.security.encode_id( tool.id ) )}">${tool.name}</a></td>
+ <td>${tool.description}</td>
+ </tr>
+ </tbody>
+ </table>
+%endif
diff -r 4c740255b9e7 -r f905e1415dd4 templates/webapps/community/tool/grid.mako
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/webapps/community/tool/grid.mako Wed Apr 14 10:20:32 2010 -0400
@@ -0,0 +1,1 @@
+<%inherit file="/grid_base.mako"/>
1
0
details: http://www.bx.psu.edu/hg/galaxy/rev/4c740255b9e7
changeset: 3646:4c740255b9e7
user: Nate Coraor <nate(a)bx.psu.edu>
date: Wed Apr 14 10:14:43 2010 -0400
description:
Remove the outdated "Revision" file
diffstat:
Revision | 11 -----------
1 files changed, 0 insertions(+), 11 deletions(-)
diffs (15 lines):
diff -r 46a391f2d696 -r 4c740255b9e7 Revision
--- a/Revision Wed Apr 14 10:14:26 2010 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-Path: .
-URL: svn+ssh://coltrane.bx.psu.edu/depot/projects/universe/svnroot/galaxy/trunk
-Repository UUID: 9bcadc22-80f8-0310-8a53-c8f022958886
-Revision: 1349
-Node Kind: directory
-Schedule: normal
-Last Changed Author: jbhe
-Last Changed Rev: 1349
-Last Changed Date: 2007-02-08 15:12:40 -0500 (Thu, 08 Feb 2007)
-Properties Last Updated: 2007-01-31 22:47:34 -0500 (Wed, 31 Jan 2007)
-
1
0
15 Apr '10
details: http://www.bx.psu.edu/hg/galaxy/rev/46a391f2d696
changeset: 3645:46a391f2d696
user: Nate Coraor <nate(a)bx.psu.edu>
date: Wed Apr 14 10:14:26 2010 -0400
description:
Add the enable_job_running param to the sample config
diffstat:
universe_wsgi.ini.sample | 5 +++++
1 files changed, 5 insertions(+), 0 deletions(-)
diffs (15 lines):
diff -r d6a527033f2c -r 46a391f2d696 universe_wsgi.ini.sample
--- a/universe_wsgi.ini.sample Wed Apr 14 09:38:35 2010 -0400
+++ b/universe_wsgi.ini.sample Wed Apr 14 10:14:26 2010 -0400
@@ -205,6 +205,11 @@
# ---- Job Execution --------------------------------------------------------
+# If running multiple Galaxy processes, one can be designated as the job
+# runner. For more information, see:
+# http://bitbucket.org/galaxy/galaxy-central/wiki/Config/LoadBalancing
+#enable_job_running = True
+
# Number of concurrent jobs to run (local job runner)
#local_job_queue_workers = 5
1
0
15 Apr '10
details: http://www.bx.psu.edu/hg/galaxy/rev/d6a527033f2c
changeset: 3644:d6a527033f2c
user: Greg Von Kuster <greg(a)bx.psu.edu>
date: Wed Apr 14 09:38:35 2010 -0400
description:
Since the handle to the tag handler has been moved to the app, eliminate the now unnecessary import of a specific tag handler from the grid helper. Clean up the grid helpr code, pass trans to methods instead of trans.sa_session so that methods can get other items from trans ( like the tag hendler ). Eliminate a seemingly unnecessary import in grid_base.mako that tightly coupled the template to the model.
diffstat:
lib/galaxy/web/framework/helpers/grids.py | 85 ++++++++++--------------------
templates/grid_base.mako | 1 -
2 files changed, 28 insertions(+), 58 deletions(-)
diffs (351 lines):
diff -r 99782dc9d022 -r d6a527033f2c lib/galaxy/web/framework/helpers/grids.py
--- a/lib/galaxy/web/framework/helpers/grids.py Tue Apr 13 20:21:57 2010 -0400
+++ b/lib/galaxy/web/framework/helpers/grids.py Wed Apr 14 09:38:35 2010 -0400
@@ -3,7 +3,6 @@
from galaxy.web.base import controller
from galaxy.web.framework.helpers import iff
-from galaxy.tags.tag_handler import GalaxyTagHandler
from galaxy.web import url_for
from galaxy.util.json import from_json_string, to_json_string
from galaxy.util.odict import odict
@@ -21,9 +20,7 @@
model_class = None
template = "grid_base.mako"
async_template = "grid_base_async.mako"
-
use_async = False
-
global_actions = []
columns = []
operations = []
@@ -32,10 +29,8 @@
default_filter = {}
default_sort_key = None
preserve_state = False
-
use_paging = False
num_rows_per_page = 25
-
# Set preference names.
cur_filter_pref_name = ".filter"
cur_sort_key_pref_name = ".sort_key"
@@ -47,12 +42,10 @@
if operation.allow_multiple:
self.has_multiple_item_operations = True
break
-
def __call__( self, trans, **kwargs ):
status = kwargs.get( 'status', None )
message = kwargs.get( 'message', None )
session = trans.sa_session
-
# Build a base filter and sort key that is the combination of the saved state and defaults. Saved state takes preference over defaults.
base_filter = {}
if self.default_filter:
@@ -63,24 +56,19 @@
if pref_name in trans.get_user().preferences:
saved_filter = from_json_string( trans.get_user().preferences[pref_name] )
base_filter.update( saved_filter )
-
pref_name = unicode( self.__class__.__name__ + self.cur_sort_key_pref_name )
if pref_name in trans.get_user().preferences:
base_sort_key = from_json_string( trans.get_user().preferences[pref_name] )
-
# Build initial query
query = self.build_initial_query( session )
query = self.apply_default_filter( trans, query, **kwargs )
-
# Maintain sort state in generated urls
extra_url_args = {}
-
# Determine whether use_default_filter flag is set.
use_default_filter_str = kwargs.get( 'use_default_filter' )
use_default_filter = False
if use_default_filter_str:
use_default_filter = ( use_default_filter_str.lower() == 'true' )
-
# Process filtering arguments to (a) build a query that represents the filter and (b) builds a
# dictionary that denotes the current filter.
cur_filter_dict = {}
@@ -96,7 +84,6 @@
column_filter = kwargs.get( "f-" + column.key )
elif column.key in base_filter:
column_filter = base_filter.get( column.key )
-
# Method (1) combines a mix of strings and lists of strings into a single string and (2) attempts to de-jsonify all strings.
def from_json_string_recurse(item):
decoded_list = []
@@ -115,8 +102,7 @@
for element in item:
a_list = from_json_string_recurse( element )
decoded_list = decoded_list + a_list
- return decoded_list
-
+ return decoded_list
# If column filter found, apply it.
if column_filter is not None:
# TextColumns may have a mix of json and strings.
@@ -131,7 +117,7 @@
if column_filter == '':
continue
# Update query.
- query = column.filter( trans.sa_session, trans.get_user(), query, column_filter )
+ query = column.filter( trans, trans.get_user(), query, column_filter )
# Upate current filter dict.
cur_filter_dict[ column.key ] = column_filter
# Carry filter along to newly generated urls; make sure filter is a string so
@@ -147,7 +133,6 @@
if not isinstance( column_filter, basestring ):
column_filter = unicode(column_filter)
extra_url_args[ "f-" + column.key ] = column_filter.encode("utf-8")
-
# Process sort arguments.
sort_key = sort_order = None
if 'sort' in kwargs:
@@ -167,10 +152,8 @@
# See reason for not using lower() to do case-insensitive search.
query = query.order_by( self.model_class.table.c.get( sort_key ).asc() )
extra_url_args['sort'] = encoded_sort_key
-
# There might be a current row
current_item = self.get_current_item( trans )
-
# Process page number.
if self.use_paging:
if 'page' in kwargs:
@@ -196,8 +179,6 @@
# Defaults.
page_num = 1
num_pages = 1
-
-
# Preserve grid state: save current filter and sort key.
if self.preserve_state:
pref_name = unicode( self.__class__.__name__ + self.cur_filter_pref_name )
@@ -207,14 +188,12 @@
pref_name = unicode( self.__class__.__name__ + self.cur_sort_key_pref_name )
trans.get_user().preferences[pref_name] = unicode( to_json_string( sort_key ) )
trans.sa_session.flush()
-
# Log grid view.
context = unicode( self.__class__.__name__ )
params = cur_filter_dict.copy()
params['sort'] = sort_key
params['async'] = ( 'async' in kwargs )
trans.log_action( trans.get_user(), unicode( "grid.view"), context, params )
-
# Render grid.
def url( *args, **kwargs ):
# Only include sort/filter arguments if not linking to another
@@ -235,7 +214,6 @@
else:
new_kwargs[ 'id' ] = trans.security.encode_id( id )
return url_for( **new_kwargs )
-
use_panels = ( 'use_panels' in kwargs ) and ( kwargs['use_panels'] == True )
async_request = ( ( self.use_async ) and ( 'async' in kwargs ) and ( kwargs['async'] in [ 'True', 'true'] ) )
return trans.fill_template( iff( async_request, self.async_template, self.template),
@@ -254,9 +232,9 @@
message_type = status,
message = message,
use_panels=use_panels,
- # Pass back kwargs so that grid template can set and use args without grid explicitly having to pass them.
- kwargs=kwargs
- )
+ # Pass back kwargs so that grid template can set and use args without
+ # grid explicitly having to pass them.
+ kwargs=kwargs )
def get_ids( self, **kwargs ):
id = []
if 'id' in kwargs:
@@ -270,7 +248,6 @@
except:
error( "Invalid id" )
return id
-
# ---- Override these ----------------------------------------------------
def handle_operation( self, trans, operation, ids ):
pass
@@ -315,7 +292,7 @@
if self.link and self.link( item ):
return self.link( item )
return None
- def filter( self, db_session, user, query, column_filter ):
+ def filter( self, trans, user, query, column_filter ):
""" Modify query to reflect the column filter. """
if column_filter == "All":
pass
@@ -335,15 +312,14 @@
class TextColumn( GridColumn ):
""" Generic column that employs freetext and, hence, supports freetext, case-independent filtering. """
- def filter( self, db_session, user, query, column_filter ):
+ def filter( self, trans, user, query, column_filter ):
""" Modify query to filter using free text, case independence. """
if column_filter == "All":
pass
elif column_filter:
- query = query.filter( self.get_filter( user, column_filter ) )
+ query = query.filter( self.get_filter( trans, user, column_filter ) )
return query
-
- def get_filter( self, user, column_filter ):
+ def get_filter( self, trans, user, column_filter ):
""" Returns a SQLAlchemy criterion derived from column_filter. """
if isinstance( column_filter, basestring ):
return self.get_single_filter( user, column_filter )
@@ -352,7 +328,6 @@
for filter in column_filter:
clause_list.append( self.get_single_filter( user, filter ) )
return and_( *clause_list )
-
def get_single_filter( self, user, a_filter ):
""" Returns a SQLAlchemy criterion derived for a single filter. Single filter is the most basic filter--usually a string--and cannot be a list. """
model_class_key_field = getattr( self.model_class, self.key )
@@ -364,12 +339,10 @@
GridColumn.__init__( self, col_name, key=key, model_class=model_class, filterable=filterable )
self.sortable = False
self.model_annotation_association_class = model_annotation_association_class
-
def get_value( self, trans, grid, item ):
""" Returns item annotation. """
annotation = self.get_item_annotation_str( trans.sa_session, item.user, item )
return iff( annotation, annotation, "" )
-
def get_single_filter( self, user, a_filter ):
""" Filter by annotation and annotation owner. """
return self.model_class.annotations.any(
@@ -390,20 +363,19 @@
def get_value( self, trans, grid, item ):
return trans.fill_template( "/tagging_common.mako", tag_type="community", trans=trans, user=trans.get_user(), tagged_item=item, elt_context=self.grid_name,
in_form=True, input_size="20", tag_click_fn="add_tag_to_grid_filter", use_toggle_link=True )
- def filter( self, db_session, user, query, column_filter ):
+ def filter( self, trans, user, query, column_filter ):
""" Modify query to filter model_class by tag. Multiple filters are ANDed. """
if column_filter == "All":
pass
elif column_filter:
- query = query.filter( self.get_filter( user, column_filter ) )
+ query = query.filter( self.get_filter( trans, user, column_filter ) )
return query
- def get_filter( self, user, column_filter ):
+ def get_filter( self, trans, user, column_filter ):
# Parse filter to extract multiple tags.
- tag_handler = GalaxyTagHandler()
if isinstance( column_filter, list ):
# Collapse list of tags into a single string; this is redundant but effective. TODO: fix this by iterating over tags.
column_filter = ",".join( column_filter )
- raw_tags = tag_handler.parse_tags( column_filter.encode("utf-8") )
+ raw_tags = trans.app.tag_handler.parse_tags( column_filter.encode( "utf-8" ) )
clause_list = []
for name, value in raw_tags.items():
if name:
@@ -417,15 +389,21 @@
class IndividualTagsColumn( CommunityTagsColumn ):
""" Column that supports individual tags. """
def get_value( self, trans, grid, item ):
- return trans.fill_template( "/tagging_common.mako", tag_type="individual", trans=trans, user=trans.get_user(), tagged_item=item, elt_context=self.grid_name,
- in_form=True, input_size="20", tag_click_fn="add_tag_to_grid_filter", use_toggle_link=True )
- def get_filter( self, user, column_filter ):
+ return trans.fill_template( "/tagging_common.mako",
+ tag_type="individual",
+ user=trans.user,
+ tagged_item=item,
+ elt_context=self.grid_name,
+ in_form=True,
+ input_size="20",
+ tag_click_fn="add_tag_to_grid_filter",
+ use_toggle_link=True )
+ def get_filter( self, trans, user, column_filter ):
# Parse filter to extract multiple tags.
- tag_handler = GalaxyTagHandler()
if isinstance( column_filter, list ):
# Collapse list of tags into a single string; this is redundant but effective. TODO: fix this by iterating over tags.
column_filter = ",".join( column_filter )
- raw_tags = tag_handler.parse_tags( column_filter.encode("utf-8") )
+ raw_tags = trans.app.tag_handler.parse_tags( column_filter.encode( "utf-8" ) )
clause_list = []
for name, value in raw_tags.items():
if name:
@@ -441,7 +419,7 @@
def __init__( self, col_name, cols_to_filter, key, visible, filterable="default" ):
GridColumn.__init__( self, col_name, key=key, visible=visible, filterable=filterable)
self.cols_to_filter = cols_to_filter
- def filter( self, db_session, user, query, column_filter ):
+ def filter( self, trans, user, query, column_filter ):
""" Modify query to filter model_class by tag. Multiple filters are ANDed. """
if column_filter == "All":
return query
@@ -450,15 +428,14 @@
for filter in column_filter:
part_clause_list = []
for column in self.cols_to_filter:
- part_clause_list.append( column.get_filter( user, filter ) )
+ part_clause_list.append( column.get_filter( trans, user, filter ) )
clause_list.append( or_( *part_clause_list ) )
complete_filter = and_( *clause_list )
else:
clause_list = []
for column in self.cols_to_filter:
- clause_list.append( column.get_filter( user, column_filter ) )
+ clause_list.append( column.get_filter( trans, user, column_filter ) )
complete_filter = or_( *clause_list )
-
return query.filter( complete_filter )
class OwnerColumn( TextColumn ):
@@ -495,7 +472,6 @@
# Delete items cannot be shared.
if item.deleted:
return ""
-
# Build a list of sharing for this item.
sharing_statuses = []
if item.users_shared_with:
@@ -505,13 +481,11 @@
if item.published:
sharing_statuses.append( "Published" )
return ", ".join( sharing_statuses )
-
def get_link( self, trans, grid, item ):
if not item.deleted and ( item.users_shared_with or item.importable or item.published ):
return dict( operation="share or publish", id=item.id )
return None
-
- def filter( self, db_session, user, query, column_filter ):
+ def filter( self, trans, user, query, column_filter ):
""" Modify query to filter histories by sharing status. """
if column_filter == "All":
pass
@@ -526,7 +500,6 @@
elif column_filter == "published":
query = query.filter( self.model_class.published == True )
return query
-
def get_accepted_filters( self ):
""" Returns a list of accepted filters for this column. """
accepted_filter_labels_and_vals = odict()
@@ -560,7 +533,6 @@
return temp
else:
return dict( operation=self.label, id=item.id )
-
def allowed( self, item ):
if self.condition:
return self.condition( item )
@@ -586,4 +558,3 @@
for k, v in self.args.items():
rval[ "f-" + k ] = v
return rval
-
diff -r 99782dc9d022 -r d6a527033f2c templates/grid_base.mako
--- a/templates/grid_base.mako Tue Apr 13 20:21:57 2010 -0400
+++ b/templates/grid_base.mako Wed Apr 14 09:38:35 2010 -0400
@@ -1,6 +1,5 @@
<%!
from galaxy.web.framework.helpers.grids import TextColumn
- from galaxy.model import History, HistoryDatasetAssociation, User, Role, Group
import galaxy.util
def inherit(context):
if context.get('use_panels'):
1
0
15 Apr '10
details: http://www.bx.psu.edu/hg/galaxy/rev/99782dc9d022
changeset: 3643:99782dc9d022
user: Kanwei Li <kanwei(a)gmail.com>
date: Tue Apr 13 20:21:57 2010 -0400
description:
- Add support for custom dbkeys using user prefs db table (currently only enabled when enable_tracks=True)
- Fix async dataset delete: dataset was disappearing when not successfully deleted
diffstat:
lib/galaxy/web/controllers/user.py | 54 +++++++++++++++++
templates/root/history.mako | 34 ++++++----
templates/user/dbkeys.mako | 94 +++++++++++++++++++++++++++++++
templates/webapps/galaxy/base_panels.mako | 3 +
4 files changed, 170 insertions(+), 15 deletions(-)
diffs (225 lines):
diff -r 122a4568c046 -r 99782dc9d022 lib/galaxy/web/controllers/user.py
--- a/lib/galaxy/web/controllers/user.py Tue Apr 13 17:29:18 2010 -0400
+++ b/lib/galaxy/web/controllers/user.py Tue Apr 13 20:21:57 2010 -0400
@@ -7,6 +7,7 @@
import logging, os, string, re
from random import choice
from galaxy.web.form_builder import *
+from galaxy.util.json import from_json_string, to_json_string
log = logging.getLogger( __name__ )
@@ -841,3 +842,56 @@
user_id=user_id,
message='Address <b>%s</b> undeleted' % user_address.desc,
status='done') )
+
+ @web.expose
+ @web.require_login()
+ def dbkeys( self, trans, **kwds ):
+ user = trans.get_user()
+ message = None
+ lines_skipped = 0
+ if 'dbkeys' not in user.preferences:
+ dbkeys = {}
+ else:
+ dbkeys = from_json_string(user.preferences['dbkeys'])
+
+ if 'delete' in kwds:
+ key = kwds.get('key', '')
+ if key and key in dbkeys:
+ del dbkeys[key]
+
+ elif 'add' in kwds:
+ name = kwds.get('name', '')
+ key = kwds.get('key', '')
+ len_file = kwds.get('len_file', None)
+ if getattr(len_file, "file", None): # Check if it's a FieldStorage object
+ len_text = len_file.file.read()
+ else:
+ len_text = kwds.get('len_text', '')
+ if not name or not key or not len_text:
+ message = "You must specify values for all the fields."
+ else:
+ chrom_dict = {}
+ for line in len_text.split("\n"):
+ lst = line.strip().split()
+ if not lst or len(lst) < 2:
+ lines_skipped += 1
+ continue
+ chrom, length = lst[0], lst[1]
+ try:
+ length = int(length)
+ except ValueError:
+ lines_skipped += 1
+ continue
+ chrom_dict[chrom] = length
+ dbkeys[key] = { "name": name, "chroms": chrom_dict }
+
+ user.preferences['dbkeys'] = to_json_string(dbkeys)
+ trans.sa_session.flush()
+
+ return trans.fill_template( 'user/dbkeys.mako',
+ user=user,
+ dbkeys=dbkeys,
+ message=message,
+ lines_skipped=lines_skipped )
+
+
\ No newline at end of file
diff -r 122a4568c046 -r 99782dc9d022 templates/root/history.mako
--- a/templates/root/history.mako Tue Apr 13 17:29:18 2010 -0400
+++ b/templates/root/history.mako Tue Apr 13 20:21:57 2010 -0400
@@ -33,21 +33,25 @@
$( '#historyItem-' + data_id + "> div.historyItemTitleBar" ).addClass( "spinner" );
$.ajax({
url: "${h.url_for( action='delete_async', id='XXX' )}".replace( 'XXX', data_id ),
- error: function() { alert( "Delete failed" ) },
- success: function() {
- %if show_deleted:
- var to_update = {};
- to_update[data_id] = "none";
- updater( to_update );
- %else:
- $( "#historyItem-" + data_id ).fadeOut( "fast", function() {
- $( "#historyItemContainer-" + data_id ).remove();
- if ( $( "div.historyItemContainer" ).length < 1 ) {
- $( "#emptyHistoryMessage" ).show();
- }
- });
- %endif
- $(".tipsy").remove();
+ error: function() { alert( "Delete failed" ); },
+ success: function(msg) {
+ if (msg === "OK") {
+ %if show_deleted:
+ var to_update = {};
+ to_update[data_id] = "none";
+ updater( to_update );
+ %else:
+ $( "#historyItem-" + data_id ).fadeOut( "fast", function() {
+ $( "#historyItemContainer-" + data_id ).remove();
+ if ( $( "div.historyItemContainer" ).length < 1 ) {
+ $( "#emptyHistoryMessage" ).show();
+ }
+ });
+ %endif
+ $(".tipsy").remove();
+ } else {
+ alert( "Delete failed" );
+ }
}
});
return false;
diff -r 122a4568c046 -r 99782dc9d022 templates/user/dbkeys.mako
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/user/dbkeys.mako Tue Apr 13 20:21:57 2010 -0400
@@ -0,0 +1,94 @@
+<%inherit file="/base.mako"/>
+<%def name="title()">Custom Database Builds</%def>
+
+<style type="text/css">
+ th, td {
+ min-width: 100px;
+ vertical-align: text-top;
+ }
+ pre {
+ padding: 0;
+ margin: 0;
+ }
+</style>
+
+<script type="text/javascript">
+
+$(function() {
+ $(".db_hide").each(function() {
+ var pre = $(this);
+ pre.hide();
+ pre.siblings("span").wrap( "<a href='javascript:void();'></a>" ).click( function() {
+ pre.toggle();
+ });
+ });
+});
+
+</script>
+
+% if message:
+ <div class="errormessagelarge">${message}</div>
+% endif
+
+% if lines_skipped > 0:
+ <div class="warningmessagelarge">Skipped ${lines_skipped} lines that could not be parsed</div>
+% endif
+
+<h2>Custom Database/Builds</h2>
+
+<p>You may specify your own database/builds here.</p>
+
+% if dbkeys:
+ <table class="colored" cellspacing="0" cellpadding="0">
+ <tr class="header">
+ <th>Name</th>
+ <th>Key</th>
+ <th>Chroms/Lengths</th>
+ <th></th>
+ </tr>
+ % for key, dct in dbkeys.iteritems():
+ <tr>
+ <td>${dct["name"] | h}</td>
+ <td>${key | h}</td>
+ <td>
+ <span>${len(dct["chroms"])} entries</span>
+ <pre id="pre_${key}" class="db_hide">
+ <table cellspacing="0" cellpadding="0">
+ <tr><th>Chrom</th><th>Length</th></tr>
+ % for chrom, chrom_len in dct["chroms"].iteritems():
+ <tr><td>${chrom | h}</td><td>${chrom_len | h}</td></tr>
+ % endfor
+ </table>
+ </pre>
+ </td>
+ <td><form action="dbkeys" method="post"><input type="hidden" name="key" value="${key}" /><input type="submit" name="delete" value="Delete" /></form></td>
+ </tr>
+ % endfor
+ </table>
+% else:
+ <p>You currently have no custom builds.</p>
+% endif
+<br />
+<form action="dbkeys" method="post" enctype="multipart/form-data">
+ <div class="toolForm">
+ <div class="toolFormTitle">Add a Build</div>
+ <div class="toolFormBody">
+ <div class="form-row">
+ <label for="name">Name (eg: Human Chromosome):</label>
+ <input type="text" id="name" name="name" />
+ </div>
+ <div class="form-row">
+ <label for="key">Key (eg: hg18):</label>
+ <input type="text" id="key" name="key" />
+ </div>
+ <div class="form-row">
+ <label for="len_file">Chromosome Length file upload (.len file):</label>
+ <input type="file" id="len_file" name="len_file" /><br />
+ <label for="len_text">Alternatively, paste length info:</label>
+ <textarea id="len_text" name="len_text" cols="40" rows="10"></textarea>
+ </div>
+
+ <div class="form-row"><input type="submit" name="add" value="Submit"/></div>
+ </div>
+ </div>
+</form>
\ No newline at end of file
diff -r 122a4568c046 -r 99782dc9d022 templates/webapps/galaxy/base_panels.mako
--- a/templates/webapps/galaxy/base_panels.mako Tue Apr 13 17:29:18 2010 -0400
+++ b/templates/webapps/galaxy/base_panels.mako Tue Apr 13 20:21:57 2010 -0400
@@ -119,6 +119,9 @@
else:
logout_url = h.url_for( controller='/user', action='logout' )
%>
+ %if app.config.get_bool( 'enable_tracks', False ):
+ <li><a target="galaxy_main" href="${h.url_for( controller='/user', action='dbkeys' )}">Custom Builds</a></li>
+ %endif
<li><a target="_top" href="${logout_url}">Logout</a></li>
%endif
<li><hr style="color: inherit; background-color: gray"/></li>
1
0