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
August 2009
- 12 participants
- 156 discussions
Hi Galaxy team,
The following is just a bunch of thoughts after using Galaxy for a
while and which might be interesting for future developments...
1. Interface consistency: "Save"
* There are three nice icons at the top of all my dataset items in the
history panel on the right for view, edit and delete. So why is there
no save icon at the same location instead of a link further down?
* When I edit a workflow there is a save button above the canvas and
there is another on in the panel on the right when I edit the
properties of a specific workflow item. As far as I can tell these
buttons are not completely redundant, but why do I need two save
buttons?
2. Provenance data
* Reproducibility is important and it is nice that Galaxy
automatically captures your analysis in histories, but if I want to
have a second look at my data after let's say a few months to figure
out what I did exactly and how a certain combination of data and tools
produced a certain result. Hence if I for example executed a workflow
once every two weeks on updated data for many months I might want to
retrieve the history for a certain version of a database. So I might
want to say give me the histories containing datasets tagged as
Ensembl version 48, or UniProt 3 or some version of a reference
assembly, etc. Or I might want to see how the results changed for a
certain gene over time as result of updated databases and /or tools.
So I might want to say to Galaxy show me all histories containing
ENSGALG000012589 or NM_45689725. Hence, I'd love to be able to search
histories. In addition to make it a bit easier to trace thing in
browse mode it would be nice if the date a history was last modified
would be visible. Currently I only have the age of the history in
minutes, hours or days. That is convenient for recent items, but for
things that are longer ago a date makes more sense to me....
* There is a fixed "Database/Build" popup that I can use to tag my
data sets, but this feels artificially limited. Is there any reason
why the species and database version cannot be separate items? If
there would be a popup first to select a species followed by a second
popup to select the genome assembly version, the lists could be a lot
smaller and hence easier to navigate.
In addition there are cases where I do have a species, but don't have
an assembly or where there are additional version numbers to keep
track of.
For example I have lots of Ensembl data. Ensembl does not have a
single version number, but 3 version numbers. There is one for the
database schema, one for the assembly and one for the annotation/
genebuild. The curent version for mouse is for example: 55 37 h, where
55 is the release and schema version number, 37 the assembly and "h"
the version of the gene build. In addition I recently moved to a
proteomics group and might want to capture DB version numbers for
species without a reference assembly. for example I might know the
species name and the fact I'm using UniProt 15.5... but currently I
cannot easily capture that in a consistent way. (I know I might add
this to the "info" for a dataset, but it's free text, with all kinds
of possible spelling variants as a result...)
Cheers,
Pi
-------------------------------------------------------------
Biomolecular Mass Spectrometry and Proteomics
Utrecht University
Visiting address:
H.R. Kruyt building room O607
Padualaan 8
3584 CH Utrecht
The Netherlands
Mail address:
P.O. box 80.082
3508 TB Utrecht
The Netherlands
phone: +31 (0)6-143 66 783
email: pieter.neerincx(a)gmail.com
skype: pieter.online
------------------------------------------------------------
4
4
Hi,
I have a local install of Galaxy and would like to keep up-to-date with
the latest updates, but I'm not sure what functionality has changed nor
how significant the changes are. The mercurial updates on this list are
too detailed. I just want to know what the benefit of an update would
be, not which files have changed.
Would it be possible to start having more managed releases with a
changelog between each release? That way it will be much clearer what
the functional benefits of updating to a newer release would be.
Cheers,
Chris
--
Dr Chris Cole
Senior Bioinformatics Research Officer
School of Life Sciences Research
University of Dundee
Dow Street
Dundee
DD1 5EH
Scotland, UK
url: http://network.nature.com/profile/drchriscole
e-mail: chris(a)compbio.dundee.ac.uk
Tel: +44 (0)1382 388 721
The University of Dundee is a registered Scottish charity, No: SC015096
4
6
29 Aug '09
details: http://www.bx.psu.edu/hg/galaxy/rev/4dc854bf2529
changeset: 2654:4dc854bf2529
user: Greg Von Kuster <greg(a)bx.psu.edu>
date: Fri Aug 28 21:08:07 2009 -0400
description:
Fix unit tests and clean up code for retrieving current user and roles.
21 file(s) affected in this change:
lib/galaxy/tools/actions/__init__.py
lib/galaxy/tools/parameters/basic.py
lib/galaxy/web/controllers/dataset.py
lib/galaxy/web/controllers/library.py
lib/galaxy/web/controllers/root.py
lib/galaxy/web/framework/__init__.py
lib/galaxy/webapps/reports/buildapp.py
templates/dataset/edit_attributes.mako
templates/library/browse_library.mako
templates/library/common.mako
templates/library/folder_info.mako
templates/library/folder_permissions.mako
templates/library/ldda_edit_info.mako
templates/library/ldda_info.mako
templates/library/library_dataset_info.mako
templates/library/library_dataset_permissions.mako
templates/library/library_info.mako
templates/library/library_permissions.mako
templates/mobile/history/detail.mako
templates/mobile/manage_library.mako
templates/root/history_common.mako
diffs (483 lines):
diff -r 6b924dd68e77 -r 4dc854bf2529 lib/galaxy/tools/actions/__init__.py
--- a/lib/galaxy/tools/actions/__init__.py Fri Aug 28 18:09:43 2009 -0400
+++ b/lib/galaxy/tools/actions/__init__.py Fri Aug 28 21:08:07 2009 -0400
@@ -47,11 +47,7 @@
assoc.dataset = new_data
assoc.flush()
data = new_data
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
if data and not trans.app.security_agent.allow_action( user,
roles,
data.permitted_actions.DATASET_ACCESS,
@@ -268,11 +264,7 @@
# parameters to the command as a special case.
for name, value in tool.params_to_strings( incoming, trans.app ).iteritems():
job.add_parameter( name, value )
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
for name, dataset in inp_data.iteritems():
if dataset:
if not trans.app.security_agent.allow_action( user,
diff -r 6b924dd68e77 -r 4dc854bf2529 lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py Fri Aug 28 18:09:43 2009 -0400
+++ b/lib/galaxy/tools/parameters/basic.py Fri Aug 28 21:08:07 2009 -0400
@@ -1137,11 +1137,7 @@
field = form_builder.SelectField( self.name, self.multiple, None, self.refresh_on_change, refresh_on_change_values = self.refresh_on_change_values )
# CRUCIAL: the dataset_collector function needs to be local to DataToolParameter.get_html_field()
def dataset_collector( hdas, parent_hid ):
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
for i, hda in enumerate( hdas ):
if len( hda.name ) > 30:
hda_name = '%s..%s' % ( hda.name[:17], hda.name[-11:] )
diff -r 6b924dd68e77 -r 4dc854bf2529 lib/galaxy/web/controllers/dataset.py
--- a/lib/galaxy/web/controllers/dataset.py Fri Aug 28 18:09:43 2009 -0400
+++ b/lib/galaxy/web/controllers/dataset.py Fri Aug 28 21:08:07 2009 -0400
@@ -108,11 +108,7 @@
data = trans.app.model.HistoryDatasetAssociation.get( dataset_id )
if not data:
raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset id: %s." % str( dataset_id ) )
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
if trans.app.security_agent.allow_action( user,
roles,
data.permitted_actions.DATASET_ACCESS,
@@ -150,11 +146,7 @@
if 'display_url' not in kwd or 'redirect_url' not in kwd:
return trans.show_error_message( 'Invalid parameters specified for "display at" link, please contact a Galaxy administrator' )
redirect_url = kwd['redirect_url'] % urllib.quote_plus( kwd['display_url'] )
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
if trans.app.security_agent.allow_action( None, None, data.permitted_actions.DATASET_ACCESS, dataset=data.dataset ):
return trans.response.send_redirect( redirect_url ) # anon access already permitted by rbac
if trans.app.security_agent.allow_action( user,
diff -r 6b924dd68e77 -r 4dc854bf2529 lib/galaxy/web/controllers/library.py
--- a/lib/galaxy/web/controllers/library.py Fri Aug 28 18:09:43 2009 -0400
+++ b/lib/galaxy/web/controllers/library.py Fri Aug 28 21:08:07 2009 -0400
@@ -62,11 +62,7 @@
params = util.Params( kwd )
msg = util.restore_text( params.get( 'msg', '' ) )
messagetype = params.get( 'messagetype', 'done' )
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
all_libraries = trans.app.model.Library.filter( trans.app.model.Library.table.c.deleted==False ) \
.order_by( trans.app.model.Library.name ).all()
authorized_libraries = []
@@ -279,11 +275,7 @@
msg=util.sanitize_text( msg ),
messagetype='error' ) )
seen = []
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
for id in ldda_ids:
ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( id )
if not ldda or not trans.app.security_agent.allow_action( user,
@@ -384,11 +376,7 @@
id=library_id,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
if action == 'information':
if params.get( 'edit_attributes_button', False ):
if trans.app.security_agent.allow_action( user,
@@ -464,11 +452,7 @@
last_used_build = replace_dataset.library_dataset_dataset_association.dbkey
else:
replace_dataset = None
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
# Let's not overwrite the imported datatypes module with the variable datatypes?
# The built-in 'id' is overwritten in lots of places as well
ldatatypes = [ dtype_name for dtype_name, dtype_value in trans.app.datatypes_registry.datatypes_by_extension.iteritems() if dtype_value.allow_datatype_change ]
@@ -939,11 +923,7 @@
# Since permissions on all LibraryDatasetDatasetAssociations must be the same at this point, we only need
# to check one of them to see if the current user can manage permissions on them.
check_ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id_list[0] )
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
if trans.app.security_agent.allow_action( user,
roles,
trans.app.security_agent.permitted_actions.LIBRARY_MANAGE,
@@ -1010,11 +990,7 @@
id=library_id,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
if action == 'new':
if params.new == 'submitted':
new_folder = trans.app.model.LibraryFolder( name=util.restore_text( params.name ),
diff -r 6b924dd68e77 -r 4dc854bf2529 lib/galaxy/web/controllers/root.py
--- a/lib/galaxy/web/controllers/root.py Fri Aug 28 18:09:43 2009 -0400
+++ b/lib/galaxy/web/controllers/root.py Fri Aug 28 21:08:07 2009 -0400
@@ -152,11 +152,7 @@
except:
return "Dataset id '%s' is invalid" %str( id )
if data:
- user = trans.user
- if user:
- roles = user.all_roles
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
if trans.app.security_agent.allow_action( user,
roles,
data.permitted_actions.DATASET_ACCESS,
@@ -192,11 +188,7 @@
if data:
child = data.get_child_by_designation( designation )
if child:
- user = trans.user
- if user:
- roles = user.all_roles
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
if trans.app.security_agent.allow_action( user,
roles,
child.permitted_actions.DATASET_ACCESS,
@@ -216,11 +208,7 @@
if 'authz_method' in kwd:
authz_method = kwd['authz_method']
if data:
- user = trans.user
- if user:
- roles = user.all_roles
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
if authz_method == 'rbac' and trans.app.security_agent.allow_action( user,
roles,
data.permitted_actions.DATASET_ACCESS,
@@ -273,11 +261,7 @@
return trans.show_error_message( "Problem retrieving dataset." )
if id is not None and data.history.user is not None and data.history.user != trans.user:
return trans.show_error_message( "This instance of a dataset (%s) in a history does not belong to you." % ( data.id ) )
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
if trans.app.security_agent.allow_action( user,
roles,
data.permitted_actions.DATASET_ACCESS,
diff -r 6b924dd68e77 -r 4dc854bf2529 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py Fri Aug 28 18:09:43 2009 -0400
+++ b/lib/galaxy/web/framework/__init__.py Fri Aug 28 21:08:07 2009 -0400
@@ -498,6 +498,14 @@
self.sa_session.flush( [ self.galaxy_session ] )
user = property( get_user, set_user )
+ def get_user_and_roles( self ):
+ user = self.get_user()
+ if user:
+ roles = user.all_roles()
+ else:
+ roles = None
+ return user, roles
+
def user_is_admin( self ):
admin_users = self.app.config.get( "admin_users", "" ).split( "," )
if self.user and admin_users and self.user.email in admin_users:
diff -r 6b924dd68e77 -r 4dc854bf2529 lib/galaxy/webapps/reports/buildapp.py
--- a/lib/galaxy/webapps/reports/buildapp.py Fri Aug 28 18:09:43 2009 -0400
+++ b/lib/galaxy/webapps/reports/buildapp.py Fri Aug 28 21:08:07 2009 -0400
@@ -11,7 +11,6 @@
from paste.util import import_string
from paste import httpexceptions
from paste.deploy.converters import asbool
-import flup.middleware.session as flup_session
import pkg_resources
log = logging.getLogger( __name__ )
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/dataset/edit_attributes.mako
--- a/templates/dataset/edit_attributes.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/dataset/edit_attributes.mako Fri Aug 28 21:08:07 2009 -0400
@@ -6,13 +6,7 @@
<%def name="stylesheets()">
${h.css( "base", "history", "autocomplete_tagging" )}
</%def>
-<%
- user = trans.user
- if user:
- user_roles = user.all_roles()
- else:
- user_roles = None
-%>
+<% user, user_roles = trans.get_user_and_roles() %>
<%def name="javascripts()">
## <!--[if lt IE 7]>
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/library/browse_library.mako
--- a/templates/library/browse_library.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/library/browse_library.mako Fri Aug 28 21:08:07 2009 -0400
@@ -4,11 +4,7 @@
from galaxy import util
from time import strftime
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
%>
<%def name="title()">Browse data library</%def>
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/library/common.mako
--- a/templates/library/common.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/library/common.mako Fri Aug 28 21:08:07 2009 -0400
@@ -14,11 +14,7 @@
elif isinstance( library_item, trans.app.model.LibraryDatasetDatasetAssociation ):
library_item_type = 'library_dataset_dataset_association'
library_item_desc = 'library dataset'
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
%>
%if widgets:
<p/>
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/library/folder_info.mako
--- a/templates/library/folder_info.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/library/folder_info.mako Fri Aug 28 21:08:07 2009 -0400
@@ -2,13 +2,7 @@
<%namespace file="/message.mako" import="render_msg" />
<%namespace file="/library/common.mako" import="render_template_info" />
-<%
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
-%>
+<% user, roles = trans.get_user_and_roles() %>
<br/><br/>
<ul class="manage-table-actions">
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/library/folder_permissions.mako
--- a/templates/library/folder_permissions.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/library/folder_permissions.mako Fri Aug 28 21:08:07 2009 -0400
@@ -2,13 +2,7 @@
<%namespace file="/message.mako" import="render_msg" />
<%namespace file="/dataset/security_common.mako" import="render_permission_form" />
-<%
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
-%>
+<% user, roles = trans.get_user_and_roles() %>
<br/><br/>
<ul class="manage-table-actions">
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/library/ldda_edit_info.mako
--- a/templates/library/ldda_edit_info.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/library/ldda_edit_info.mako Fri Aug 28 21:08:07 2009 -0400
@@ -3,13 +3,7 @@
<%namespace file="/library/common.mako" import="render_template_info" />
<% from galaxy import util %>
-<%
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
-%>
+<% user, roles = trans.get_user_and_roles() %>
%if ldda == ldda.library_dataset.library_dataset_dataset_association:
<b><i>This is the latest version of this library dataset</i></b>
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/library/ldda_info.mako
--- a/templates/library/ldda_info.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/library/ldda_info.mako Fri Aug 28 21:08:07 2009 -0400
@@ -8,11 +8,7 @@
current_version = True
else:
current_version = False
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
%>
%if current_version:
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/library/library_dataset_info.mako
--- a/templates/library/library_dataset_info.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/library/library_dataset_info.mako Fri Aug 28 21:08:07 2009 -0400
@@ -2,13 +2,7 @@
<%namespace file="/message.mako" import="render_msg" />
<%namespace file="/library/common.mako" import="render_template_info" />
-<%
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
-%>
+<% user, roles = trans.get_user_and_roles() %>
%if library_dataset == library_dataset.library_dataset_dataset_association.library_dataset:
<b><i>This is the latest version of this library dataset</i></b>
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/library/library_dataset_permissions.mako
--- a/templates/library/library_dataset_permissions.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/library/library_dataset_permissions.mako Fri Aug 28 21:08:07 2009 -0400
@@ -2,13 +2,7 @@
<%namespace file="/message.mako" import="render_msg" />
<%namespace file="/dataset/security_common.mako" import="render_permission_form" />>
-<%
- user = trans.user
- if user:
- user_roles = user.all_roles()
- else:
- user_roles = None
-%>
+<% user, roles = trans.get_user_and_roles() %>
%if library_dataset == library_dataset.library_dataset_dataset_association.library_dataset:
<b><i>This is the latest version of this library dataset</i></b>
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/library/library_info.mako
--- a/templates/library/library_info.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/library/library_info.mako Fri Aug 28 21:08:07 2009 -0400
@@ -2,13 +2,7 @@
<%namespace file="/message.mako" import="render_msg" />
<%namespace file="/library/common.mako" import="render_template_info" />
-<%
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
-%>
+<% user, roles = trans.get_user_and_roles() %>
<br/><br/>
<ul class="manage-table-actions">
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/library/library_permissions.mako
--- a/templates/library/library_permissions.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/library/library_permissions.mako Fri Aug 28 21:08:07 2009 -0400
@@ -2,13 +2,7 @@
<%namespace file="/message.mako" import="render_msg" />
<%namespace file="/dataset/security_common.mako" import="render_permission_form" />
-<%
- user = trans.user
- if user:
- user_roles = user.all_roles()
- else:
- user_roles = None
-%>
+<% user, roles = trans.get_user_and_roles() %>
<br/><br/>
<ul class="manage-table-actions">
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/mobile/history/detail.mako
--- a/templates/mobile/history/detail.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/mobile/history/detail.mako Fri Aug 28 21:08:07 2009 -0400
@@ -36,13 +36,7 @@
<div class="secondary">
## Body for history items, extra info and actions, data "peek"
- <%
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
- %>
+ <% user, roles = trans.get_user_and_roles() %>
%if not trans.app.security_agent.allow_action( user, roles, data.permitted_actions.DATASET_ACCESS, dataset = data.dataset ):
<div>You do not have permission to view this dataset.</div>
%elif data_state == "queued":
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/mobile/manage_library.mako
--- a/templates/mobile/manage_library.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/mobile/manage_library.mako Fri Aug 28 21:08:07 2009 -0400
@@ -3,13 +3,7 @@
<%namespace file="/dataset/security_common.mako" import="render_permission_form" />
<%namespace file="/library/common.mako" import="render_template_info" />
-<%
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
-%>
+<% user, roles = trans.get_user_and_roles() %>
%if msg:
${render_msg( msg, messagetype )}
diff -r 6b924dd68e77 -r 4dc854bf2529 templates/root/history_common.mako
--- a/templates/root/history_common.mako Fri Aug 28 18:09:43 2009 -0400
+++ b/templates/root/history_common.mako Fri Aug 28 21:08:07 2009 -0400
@@ -6,11 +6,7 @@
data_state = "queued"
else:
data_state = data.state
- user = trans.user
- if user:
- roles = user.all_roles()
- else:
- roles = None
+ user, roles = trans.get_user_and_roles()
%>
%if not trans.app.security_agent.allow_action( user, roles, data.permitted_actions.DATASET_ACCESS, dataset = data.dataset ):
<div class="historyItemWrapper historyItem historyItem-${data_state} historyItem-noPermission" id="historyItem-${data.id}">
1
0
details: http://www.bx.psu.edu/hg/galaxy/rev/e383b1e2f8b0
changeset: 2650:e383b1e2f8b0
user: jeremy goecks <jeremy.goecks(a)emory.edu>
date: Fri Aug 28 15:44:28 2009 -0400
description:
Packed JS files for tagging
2 file(s) affected in this change:
static/scripts/packed/autocomplete_tagging.js
static/scripts/packed/jquery.autocomplete.js
diffs (12 lines):
diff -r 3ef81a4d574e -r e383b1e2f8b0 static/scripts/packed/autocomplete_tagging.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/scripts/packed/autocomplete_tagging.js Fri Aug 28 15:44:28 2009 -0400
@@ -0,0 +1,1 @@
+var ac_tag_area_id_gen=1;jQuery.fn.autocomplete_tagging=function(c){var e={get_toggle_link_text_fn:function(u){var w="";var v=o(u);if(v!=0){w=v+(v!=0?" Tags":" Tag")}else{w="Add tags"}return w},tag_click_fn:function(u){},input_size:20,in_form:false,tags:{},use_toggle_link:true,item_id:"",add_tag_img:"",add_tag_img_rollover:"",delete_tag_img:"",ajax_autocomplete_tag_url:"",ajax_retag_url:"",ajax_delete_tag_url:"",ajax_add_tag_url:""};var p=jQuery.extend(e,c);var k="tag-area-"+(ac_tag_area_id_gen)++;var m=$("<div></div>").attr("id",k).addClass("tag-area");this.append(m);var o=function(u){if(u.length){return u.length}var v=0;for(element in u){v++}return v};var b=function(){var u=p.get_toggle_link_text_fn(p.tags);var v=$("<a href='/history/tags'>"+u+"</a>").addClass("toggle-link");v.click(function(){var w=(m.css("display")=="none");var x;if(w){x=function(){var y=o(p.tags);if(y==0){m.click()}}}else{x=function(){m.blur()}}m.slideToggle("fast",x);return false});return v};var s=b();
if(p.use_toggle_link){this.prepend(s)}var t=function(u){var v=new Array();for(key in u){v[v.length]=key+"-->"+u[key]}return"{"+v.join(",")+"}"};var a=function(v,u){return v+((u!=""&&u)?":"+u:"")};var h=function(u){return u.split(":")};var i=function(u){var v=$("<img src='"+p.add_tag_img+"' rollover='"+p.add_tag_img_rollover+"'/>").addClass("add-tag-button");v.click(function(){$(this).hide();m.click();return false});return v};var j=function(u){var v=$("<img src='"+p.delete_tag_img+"'/>").addClass("delete-tag-img");v.mouseenter(function(){$(this).attr("src",p.delete_tag_img_rollover)});v.mouseleave(function(){$(this).attr("src",p.delete_tag_img)});v.click(function(){var B=$(this).parent();var A=B.find(".tag-name").eq(0);var z=A.text();var C=h(z)[0];B.remove();delete p.tags[C];var y=p.get_toggle_link_text_fn(p.tags);s.text(y);$.ajax({url:p.ajax_delete_tag_url,data:{tag_name:C},error:function(){alert("Remove tag failed")},success:function(){}});return true});var w=$("<span>"+u+"
</span>").addClass("tag-name");w.click(function(){p.tag_click_fn(u);return true});var x=$("<span></span>").addClass("tag-button");x.append(w);x.append(v);return x};var d=function(v){var u;if(p.in_form){u=$("<textarea id='history-tag-input' rows='1' cols='"+p.input_size+"' value='"+v+"'></textarea>")}else{u=$("<input id='history-tag-input' type='text' size='"+p.input_size+"' value='"+v+"'></input>")}u.keyup(function(D){if(D.keyCode==27){$(this).trigger("blur")}else{if((D.keyCode==13)||(D.keyCode==188)||(D.keyCode==32)){new_value=this.value;if(return_key_pressed_for_autocomplete==true){return_key_pressed_for_autocomplete=false;return false}if(new_value.indexOf(": ",new_value.length-2)!=-1){this.value=new_value.substring(0,new_value.length-1);return false}if((D.keyCode==188)||(D.keyCode==32)){new_value=new_value.substring(0,new_value.length-1)}new_value=new_value.replace(/^\s+|\s+$/g,"");if(new_value.length<3){return false}this.value="";var A=j(new_value);var z=m.children(".tag
-button");if(z.length!=0){var E=z.slice(z.length-1);E.after(A)}else{m.prepend(A)}var y=new_value.split(":");p.tags[y[0]]=y[1];var B=p.get_toggle_link_text_fn(p.tags);s.text(B);var C=$(this);$.ajax({url:p.ajax_add_tag_url,data:{new_tag:new_value},error:function(){A.remove();var F=p.get_toggle_link_text_fn(p.tags);s.text(F);alert("Add tag failed")},success:function(){C.flushCache()}});return false}}});var w=function(A,z,y,C,B){tag_name_and_value=C.split(":");return(tag_name_and_value.length==1?tag_name_and_value[0]:tag_name_and_value[1])};var x={selectFirst:false,formatItem:w,autoFill:false,highlight:false};u.autocomplete(p.ajax_autocomplete_tag_url,x);u.addClass("tag-input");return u};for(tag_name in p.tags){var q=p.tags[tag_name];var l=a(tag_name,q);var g=j(l,s,p.tags);m.append(g)}var n=d("");var f=i(n);m.blur(function(u){f.show();n.hide();m.removeClass("active-tag-area")});m.append(f);m.append(n);n.hide();m.click(function(w){var v=$(this).hasClass("active-tag-area");if($(w.
target).hasClass("delete-tag-img")&&!v){return false}if($(w.target).hasClass("tag-name")&&!v){return false}$(this).addClass("active-tag-area");f.hide();n.show();n.focus();var u=function(y){var x=m.attr("id");if(($(y.target).attr("id")!=x)&&($(y.target).parents().filter(x).length==0)){m.blur();$(document).unbind("click",u)}};$(window).click(u);return false});if(p.use_toggle_link){m.hide()}else{var r=o(p.tags);if(r==0){f.hide();n.show()}}return this.addClass("tag-element")};
\ No newline at end of file
diff -r 3ef81a4d574e -r e383b1e2f8b0 static/scripts/packed/jquery.autocomplete.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/scripts/packed/jquery.autocomplete.js Fri Aug 28 15:44:28 2009 -0400
@@ -0,0 +1,1 @@
+String.prototype.endsWith=function(a){return(this.match(a+"$")==a)};var return_key_pressed_for_autocomplete=false;(function(a){a.fn.extend({autocomplete:function(b,c){var d=typeof b=="string";c=a.extend({},a.Autocompleter.defaults,{url:d?b:null,data:d?null:b,delay:d?a.Autocompleter.defaults.delay:10,max:c&&!c.scroll?10:150},c);c.highlight=c.highlight||function(e){return e};c.formatMatch=c.formatMatch||c.formatItem;return this.each(function(){new a.Autocompleter(this,c)})},result:function(b){return this.bind("result",b)},search:function(b){return this.trigger("search",[b])},flushCache:function(){return this.trigger("flushCache")},setOptions:function(b){return this.trigger("setOptions",[b])},unautocomplete:function(){return this.trigger("unautocomplete")}});a.Autocompleter=function(l,g){var c={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8,COLON:16};var b=a(l).attr("autocomplete","off").addClass(g.inputClass);var j;var p="";var m=a.Auto
completer.Cache(g);var e=0;var u;var x={mouseDownOnSelect:false};var r=a.Autocompleter.Select(g,l,d,x);var w;a.browser.opera&&a(l.form).bind("submit.autocomplete",function(){if(w){w=false;return false}});b.bind((a.browser.opera?"keypress":"keydown")+".autocomplete",function(y){u=y.keyCode;switch(y.keyCode){case c.UP:y.preventDefault();if(r.visible()){r.prev()}else{t(0,true)}break;case c.DOWN:y.preventDefault();if(r.visible()){r.next()}else{t(0,true)}break;case c.PAGEUP:y.preventDefault();if(r.visible()){r.pageUp()}else{t(0,true)}break;case c.PAGEDOWN:y.preventDefault();if(r.visible()){r.pageDown()}else{t(0,true)}break;case g.multiple&&a.trim(g.multipleSeparator)==","&&c.COMMA:case c.TAB:case c.RETURN:if(y.keyCode==c.RETURN){return_key_pressed_for_autocomplete=false}if(d()){y.preventDefault();w=true;if(y.keyCode==c.RETURN){return_key_pressed_for_autocomplete=true}return false}case c.ESC:r.hide();break;case c.COLON:break;default:clearTimeout(j);j=setTimeout(t,g.delay);break}})
.focus(function(){e++}).blur(function(){e=0;if(!x.mouseDownOnSelect){r.hide()}return this}).click(function(){if(e++>1&&!r.visible()){t(0,true)}return this}).bind("search",function(){var y=(arguments.length>1)?arguments[1]:null;function z(D,C){var A;if(C&&C.length){for(var B=0;B<C.length;B++){if(C[B].result.toLowerCase()==D.toLowerCase()){A=C[B];break}}}if(typeof y=="function"){y(A)}else{b.trigger("result",A&&[A.data,A.value])}}a.each(h(b.val()),function(A,B){f(B,z,z)});return this}).bind("flushCache",function(){m.flush()}).bind("setOptions",function(){a.extend(g,arguments[1]);if("data" in arguments[1]){m.populate()}}).bind("unautocomplete",function(){r.unbind();b.unbind();a(l.form).unbind(".autocomplete")});function d(){var z=r.selected();if(!z){return false}var y=z.result;p=y;if(g.multiple){var A=h(b.val());if(A.length>1){y=A.slice(0,A.length-1).join(g.multipleSeparator)+g.multipleSeparator+y}y+=g.multipleSeparator}b.val(y);v();b.trigger("result",[z.data,z.value]);return tr
ue}function t(A,z){if(u==c.DEL){r.hide();return}var y=b.val();if(!z&&y==p){return}p=y;y=i(y);if(y.length>=g.minChars){b.addClass(g.loadingClass);if(!g.matchCase){y=y.toLowerCase()}f(y,k,v)}else{n();r.hide()}}function h(z){if(!z){return[""]}var A=z.split(g.multipleSeparator);var y=[];a.each(A,function(B,C){if(a.trim(C)){y[B]=a.trim(C)}});return y}function i(y){if(!g.multiple){return y}var z=h(y);return z[z.length-1]}function q(y,z){if(g.autoFill&&(i(b.val()).toLowerCase()==y.toLowerCase())&&u!=c.BACKSPACE){b.val(b.val()+z.substring(i(p).length));a.Autocompleter.Selection(l,p.length,p.length+z.length)}}function s(){clearTimeout(j);j=setTimeout(v,200)}function v(){var y=r.visible();r.hide();clearTimeout(j);n();if(g.mustMatch){b.search(function(z){if(!z){if(g.multiple){var A=h(b.val()).slice(0,-1);b.val(A.join(g.multipleSeparator)+(A.length?g.multipleSeparator:""))}else{b.val("")}}})}if(y){a.Autocompleter.Selection(l,l.value.length,l.value.length)}}function k(z,y){if(y&&y.length
&&e){n();r.display(y,z);q(z,y[0].value);r.show()}else{v()}}function f(z,B,y){if(!g.matchCase){z=z.toLowerCase()}var A=m.load(z);if(z.endsWith(":")){A=null}if(A&&A.length){B(z,A)}else{if((typeof g.url=="string")&&(g.url.length>0)){var C={timestamp:+new Date()};a.each(g.extraParams,function(D,E){C[D]=typeof E=="function"?E():E});a.ajax({mode:"abort",port:"autocomplete"+l.name,dataType:g.dataType,url:g.url,data:a.extend({q:i(z),limit:g.max},C),success:function(E){var D=g.parse&&g.parse(E)||o(E);m.add(z,D);B(z,D)}})}else{r.emptyList();y(z)}}}function o(B){var y=[];var A=B.split("\n");for(var z=0;z<A.length;z++){var C=a.trim(A[z]);if(C){C=C.split("|");y[y.length]={data:C,value:C[0],result:g.formatResult&&g.formatResult(C,C[0])||C[0]}}}return y}function n(){b.removeClass(g.loadingClass)}};a.Autocompleter.defaults={inputClass:"ac_input",resultsClass:"ac_results",loadingClass:"ac_loading",minChars:1,delay:400,matchCase:false,matchSubset:true,matchContains:false,cacheLength:10,max:10
0,mustMatch:false,extraParams:{},selectFirst:true,formatItem:function(b){return b[0]},formatMatch:null,autoFill:false,width:0,multiple:false,multipleSeparator:", ",highlight:function(c,b){return c.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)("+b.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"<strong>$1</strong>")},scroll:true,scrollHeight:180};a.Autocompleter.Cache=function(c){var f={};var d=0;function h(l,k){if(!c.matchCase){l=l.toLowerCase()}var j=l.indexOf(k);if(j==-1){return false}return j==0||c.matchContains}function g(j,i){if(d>c.cacheLength){b()}if(!f[j]){d++}f[j]=i}function e(){if(!c.data){return false}var k={},j=0;if(!c.url){c.cacheLength=1}k[""]=[];for(var m=0,l=c.data.length;m<l;m++){var p=c.data[m];p=(typeof p=="string")?[p]:p;var o=c.formatMatch(p,m+1,c.data.length);if(o===false){continue}var n=o.charAt(0).toLowerCase();if(!k[n]){k[n]=[]}var q={value:o,data:p,result:c.formatResult&&c.formatResult(p)||o};k[n].push(q);if(j++<c.
max){k[""].push(q)}}a.each(k,function(r,s){c.cacheLength++;g(r,s)})}setTimeout(e,25);function b(){f={};d=0}return{flush:b,add:g,populate:e,load:function(n){if(!c.cacheLength||!d){return null}if(!c.url&&c.matchContains){var m=[];for(var j in f){if(j.length>0){var o=f[j];a.each(o,function(p,k){if(h(k.value,n)){m.push(k)}})}}return m}else{if(f[n]){return f[n]}else{if(c.matchSubset){for(var l=n.length-1;l>=c.minChars;l--){var o=f[n.substr(0,l)];if(o){var m=[];a.each(o,function(p,k){if((k.data.indexOf("#Header")==0)||(h(k.value,n))){m[m.length]=k}});return m}}}}}return null}}};a.Autocompleter.Select=function(e,j,l,q){var i={ACTIVE:"ac_over"};var k,f=-1,s,m="",t=true,c,p;function o(){if(!t){return}c=a("<div/>").hide().addClass(e.resultsClass).css("position","absolute").appendTo(document.body);p=a("<ul/>").appendTo(c).mouseover(function(u){if(r(u).nodeName&&r(u).nodeName.toUpperCase()=="LI"){f=a("li",p).removeClass(i.ACTIVE).index(r(u));if(!n(f)){a(r(u)).addClass(i.ACTIVE)}}}).clic
k(function(u){f=a("li",p).index(r(u));if(n(f)){return}a(r(u)).addClass(i.ACTIVE);l();j.focus();return false}).mousedown(function(){q.mouseDownOnSelect=true}).mouseup(function(){q.mouseDownOnSelect=false});if(e.width>0){c.css("width",e.width)}t=false}function r(v){var u=v.target;while(u&&u.tagName!="LI"){u=u.parentNode}if(!u){return[]}return u}function n(u){dataAtPosition=s[u].data;return(dataAtPosition.indexOf("#Header")==0)}function h(u){k.slice(f,f+1).removeClass(i.ACTIVE);var v=false;do{g(u);v=n(f)}while(v);var x=k.slice(f,f+1).addClass(i.ACTIVE);if(e.scroll){var w=0;k.slice(0,f).each(function(){w+=this.offsetHeight});if((w+x[0].offsetHeight-p.scrollTop())>p[0].clientHeight){p.scrollTop(w+x[0].offsetHeight-p.innerHeight())}else{if(w<p.scrollTop()){p.scrollTop(w)}}}}function g(u){f+=u;if(f<0){f=k.size()-1}else{if(f>=k.size()){f=0}}}function b(u){return e.max&&e.max<u?e.max:u}function d(){p.empty();var v=b(s.length);for(var w=0;w<v;w++){if(!s[w]){continue}var x=e.formatItem
(s[w].data,w+1,v,s[w].value,m);if(x===false){continue}if(n(w)){if(w!=v-1){var u=a("<li/>").html(s[w].data[1]).addClass("ac_header").appendTo(p)[0]}}else{var u=a("<li/>").html(e.highlight(x,m)).addClass(w%2==0?"ac_even":"ac_odd").appendTo(p)[0]}a.data(u,"ac_data",s[w])}k=p.find("li");if(e.selectFirst){k.slice(0,1).addClass(i.ACTIVE);f=0}if(a.fn.bgiframe){p.bgiframe()}}return{display:function(v,u){o();s=v;m=u;d()},next:function(){h(1)},prev:function(){h(-1)},pageUp:function(){if(f!=0&&f-8<0){h(-f)}else{h(-8)}},pageDown:function(){if(f!=k.size()-1&&f+8>k.size()){h(k.size()-1-f)}else{h(8)}},hide:function(){c&&c.hide();k&&k.removeClass(i.ACTIVE);f=-1},visible:function(){return c&&c.is(":visible")},current:function(){return this.visible()&&(k.filter("."+i.ACTIVE)[0]||e.selectFirst&&k[0])},show:function(){var w=a(j).offset();c.css({width:typeof e.width=="string"||e.width>0?e.width:a(j).width(),top:w.top+j.offsetHeight,left:w.left}).show();if(e.scroll){p.scrollTop(0);p.css({maxHeigh
t:e.scrollHeight,overflow:"auto"});if(a.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var u=0;k.each(function(){u+=this.offsetHeight});var v=u>e.scrollHeight;p.css("height",v?e.scrollHeight:u);if(!v){k.width(p.width()-parseInt(k.css("padding-left"))-parseInt(k.css("padding-right")))}}}},selected:function(){var u=k&&k.filter("."+i.ACTIVE).removeClass(i.ACTIVE);return u&&u.length&&a.data(u[0],"ac_data")},emptyList:function(){p&&p.empty()},unbind:function(){c&&c.remove()}}};a.Autocompleter.Selection=function(d,e,c){if(d.createTextRange){var b=d.createTextRange();b.collapse(true);b.moveStart("character",e);b.moveEnd("character",c);b.select()}else{if(d.setSelectionRange){d.setSelectionRange(e,c)}else{if(d.selectionStart){d.selectionStart=e;d.selectionEnd=c}}}d.focus()}})(jQuery);
\ No newline at end of file
1
0
details: http://www.bx.psu.edu/hg/galaxy/rev/f0a17cdd31a9
changeset: 2651:f0a17cdd31a9
user: jeremy goecks <jeremy.goecks(a)emory.edu>
date: Fri Aug 28 15:55:37 2009 -0400
description:
merge
0 file(s) affected in this change:
diffs (152 lines):
diff -r e383b1e2f8b0 -r f0a17cdd31a9 eggs.ini
--- a/eggs.ini Fri Aug 28 15:44:28 2009 -0400
+++ b/eggs.ini Fri Aug 28 15:55:37 2009 -0400
@@ -26,13 +26,12 @@
numpy = 1.2.1
[eggs:noplatform]
-Beaker = 0.5
+Beaker = 1.4
docutils = 0.4
elementtree = 1.2.6_20050316
-flup = 0.5
lrucache = 0.2
;lsprof - james
-Mako = 0.1.10
+Mako = 0.2.4
MyghtyUtils = 0.52
nose = 0.9.1
NoseHTML = 0.2
@@ -76,12 +75,11 @@
python_lzo = http://www.oberhumer.com/opensource/lzo/download/LZO-v1/python-lzo-1.08.tar… http://www.oberhumer.com/opensource/lzo/download/LZO-v1/lzo-1.08.tar.gz
threadframe = http://www.majid.info/python/threadframe/threadframe-0.2.tar.gz
guppy = http://pypi.python.org/packages/source/g/guppy/guppy-0.1.8.tar.gz
-Beaker = http://cheeseshop.python.org/packages/source/B/Beaker/Beaker-0.5.tar.gz
+Beaker = http://cheeseshop.python.org/packages/source/B/Beaker/Beaker-1.4.tar.gz
docutils = http://downloads.sourceforge.net/docutils/docutils-0.4.tar.gz
elementtree = http://effbot.org/downloads/elementtree-1.2.6-20050316.tar.gz
-flup = http://www.saddi.com/software/flup/dist/archive/flup-r2311.tar.gz
lrucache = http://evan.prodromou.name/lrucache/lrucache-0.2.tar.gz
-Mako = http://www.makotemplates.org/downloads/Mako-0.1.10.tar.gz
+Mako = http://www.makotemplates.org/downloads/Mako-0.2.4.tar.gz
MyghtyUtils = http://cheeseshop.python.org/packages/source/M/MyghtyUtils/MyghtyUtils-0.52…
nose = http://www.somethingaboutorange.com/mrl/projects/nose/nose-0.9.1.tar.gz
NoseHTML = http://dist.g2.bx.psu.edu/nosehtml-0.2.tar.bz2
diff -r e383b1e2f8b0 -r f0a17cdd31a9 lib/galaxy/datatypes/images.py
--- a/lib/galaxy/datatypes/images.py Fri Aug 28 15:44:28 2009 -0400
+++ b/lib/galaxy/datatypes/images.py Fri Aug 28 15:55:37 2009 -0400
@@ -9,6 +9,7 @@
from galaxy.datatypes.sniff import *
from urllib import urlencode, quote_plus
import zipfile
+import os, subprocess, tempfile
log = logging.getLogger(__name__)
@@ -240,6 +241,26 @@
"""Class describing a BAM binary file"""
file_ext = "bam"
MetadataElement( name="bam_index", desc="BAM Index File", param=metadata.FileParameter, readonly=True, no_value=None, visible=False, optional=True )
+ def init_meta( self, dataset, copy_from=None ):
+ data.Binary.init_meta( self, dataset, copy_from=copy_from )
+ def set_meta( self, dataset, overwrite = True, **kwd ):
+ """
+ Sets index for BAM file.
+ """
+ index_file = dataset.metadata.bam_index
+ if not index_file:
+ index_file = dataset.metadata.spec['bam_index'].param.new_file( dataset = dataset )
+ tmp_dir = tempfile.gettempdir()
+ tmpf1 = tempfile.NamedTemporaryFile(dir=tmp_dir)
+ try:
+ subprocess.check_call(['cd', tmp_dir], shell=True)
+ subprocess.check_call('cp %s %s' % (dataset.file_name, tmpf1.name), shell=True)
+ subprocess.check_call('samtools index %s' % tmpf1.name, shell=True)
+ subprocess.check_call('cp %s.bai %s' % (tmpf1.name, index_file.file_name), shell=True)
+ except subprocess.CalledProcessError:
+ sys.stderr.write('There was a problem creating the index for the BAM file\n')
+ tmpf1.close()
+ dataset.metadata.bam_index = index_file
def set_peek( self, dataset ):
if not dataset.dataset.purged:
export_url = "/history_add_to?" + urlencode({'history_id':dataset.history_id,'ext':'bam','name':'bam alignments','info':'Alignments file','dbkey':dataset.dbkey})
@@ -256,4 +277,3 @@
def get_mime(self):
"""Returns the mime type of the datatype"""
return 'application/octet-stream'
-
\ No newline at end of file
diff -r e383b1e2f8b0 -r f0a17cdd31a9 lib/galaxy/web/buildapp.py
--- a/lib/galaxy/web/buildapp.py Fri Aug 28 15:44:28 2009 -0400
+++ b/lib/galaxy/web/buildapp.py Fri Aug 28 15:55:37 2009 -0400
@@ -11,7 +11,6 @@
from paste.util import import_string
from paste import httpexceptions
from paste.deploy.converters import asbool
-import flup.middleware.session as flup_session
import pkg_resources
log = logging.getLogger( __name__ )
@@ -116,17 +115,6 @@
from paste import recursive
app = recursive.RecursiveMiddleware( app, conf )
log.debug( "Enabling 'recursive' middleware" )
- ## # Session middleware puts a session factory into the environment
- ## if asbool( conf.get( 'use_session', True ) ):
- ## store = flup_session.MemorySessionStore()
- ## app = flup_session.SessionMiddleware( store, app )
- ## log.debug( "Enabling 'flup session' middleware" )
- # Beaker session middleware
- if asbool( conf.get( 'use_beaker_session', False ) ):
- pkg_resources.require( "Beaker" )
- import beaker.session
- app = beaker.session.SessionMiddleware( app, conf )
- log.debug( "Enabling 'beaker session' 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
diff -r e383b1e2f8b0 -r f0a17cdd31a9 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py Fri Aug 28 15:44:28 2009 -0400
+++ b/lib/galaxy/web/framework/__init__.py Fri Aug 28 15:55:37 2009 -0400
@@ -583,12 +583,12 @@
data.update( kwargs )
## return template.render( **data )
def render( environ, start_response ):
- response_write = start_response( self.response.wsgi_status(),
- self.response.wsgi_headeritems() )
- class C:
- def write( self, *args, **kwargs ):
- response_write( *args, **kwargs )
- context = mako.runtime.Context( C(), **data )
+ response_write = start_response( self.response.wsgi_status(), self.response.wsgi_headeritems() )
+ class StreamBuffer( object ):
+ def write( self, d ):
+ response_write( d.encode( 'utf-8' ) )
+ buffer = StreamBuffer()
+ context = mako.runtime.Context( buffer, **data )
template.render_context( context )
return []
return render
diff -r e383b1e2f8b0 -r f0a17cdd31a9 lib/galaxy/web/framework/base.py
--- a/lib/galaxy/web/framework/base.py Fri Aug 28 15:44:28 2009 -0400
+++ b/lib/galaxy/web/framework/base.py Fri Aug 28 15:55:37 2009 -0400
@@ -13,7 +13,6 @@
import pkg_resources;
pkg_resources.require( "Paste" )
pkg_resources.require( "Routes" )
-pkg_resources.require( "flup" )
pkg_resources.require( "WebOb" )
import routes
diff -r e383b1e2f8b0 -r f0a17cdd31a9 templates/root/history_common.mako
--- a/templates/root/history_common.mako Fri Aug 28 15:44:28 2009 -0400
+++ b/templates/root/history_common.mako Fri Aug 28 15:55:37 2009 -0400
@@ -35,7 +35,7 @@
<a class="icon-button delete" title="delete" href="${h.url_for( action='delete', id=data.id, show_deleted_on_refresh=show_deleted_on_refresh )}" id="historyItemDeleter-${data.id}"></a>
</div>
<span class="state-icon"></span>
- <span class="historyItemTitle"><b>${hid}: ${data.display_name()}</b></span>
+ <span class="historyItemTitle"><b>${hid}: ${data.display_name().decode('utf-8')}</b></span>
</div>
## Body for history items, extra info and actions, data "peek"
1
0
29 Aug '09
details: http://www.bx.psu.edu/hg/galaxy/rev/fba947d16fa7
changeset: 2652:fba947d16fa7
user: James Taylor <james(a)jamestaylor.org>
date: Fri Aug 28 18:03:28 2009 -0400
description:
Automated merge with ssh://scofield.bx.psu.edu//depot/projects/universe/hg
3 file(s) affected in this change:
lib/galaxy/model/__init__.py
lib/galaxy/model/mapping.py
templates/dataset/edit_attributes.mako
diffs (2309 lines):
diff -r cc4944a62b66 -r fba947d16fa7 lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py Fri Aug 28 16:52:58 2009 -0400
+++ b/lib/galaxy/model/__init__.py Fri Aug 28 18:03:28 2009 -0400
@@ -1146,6 +1146,37 @@
self.user = None
self.title = None
self.content = None
+
+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=%s, parent_id=%s, name=%s)" % ( self.id, self.type, self.parent_id, self.name )
+
+class ItemTagAssociation ( object ):
+ def __init__( self, item_id=None, tag_id=None, user_tname=None, value=None ):
+ self.item_id = item_id
+ self.tag_id = tag_id
+ self.user_tname = user_tname
+ self.value = None
+ self.user_value = None
+
+ def __str__ ( self ):
+ return "%s(item_id=%s, item_tag=%s, user_tname=%s, value=%s, user_value=%s)" % (self.__class__.__name__, self.item_id, self.tag_id, self.user_tname, self.value. self.user_value )
+
+
+class HistoryTagAssociation ( ItemTagAssociation ):
+ pass
+
+class DatasetTagAssociation ( ItemTagAssociation ):
+ pass
+
+class HistoryDatasetAssociationTagAssociation ( ItemTagAssociation ):
+ pass
diff -r cc4944a62b66 -r fba947d16fa7 lib/galaxy/model/mapping.py
--- a/lib/galaxy/model/mapping.py Fri Aug 28 16:52:58 2009 -0400
+++ b/lib/galaxy/model/mapping.py Fri Aug 28 18:03:28 2009 -0400
@@ -544,6 +544,34 @@
Column( "content", TEXT )
)
+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" ) )
+
+HistoryTagAssociation.table = Table( "history_tag_association", metadata,
+ Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ),
+ Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ),
+ Column( "user_tname", TrimmedString(255), index=True),
+ Column( "value", TrimmedString(255), index=True),
+ Column( "user_value", TrimmedString(255), index=True) )
+
+DatasetTagAssociation.table = Table( "dataset_tag_association", metadata,
+ Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ),
+ Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ),
+ Column( "user_tname", TrimmedString(255), index=True),
+ Column( "value", TrimmedString(255), index=True),
+ Column( "user_value", TrimmedString(255), index=True) )
+
+HistoryDatasetAssociationTagAssociation.table = Table( "history_dataset_association_tag_association", metadata,
+ Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ),
+ Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ),
+ Column( "user_tname", TrimmedString(255), index=True),
+ Column( "value", TrimmedString(255), index=True),
+ Column( "user_value", TrimmedString(255), index=True) )
+
# With the tables defined we can define the mappers and setup the
# relationships between the model objects.
@@ -643,7 +671,8 @@
backref=backref( "parent", primaryjoin=( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ), remote_side=[HistoryDatasetAssociation.table.c.id], uselist=False ) ),
visible_children=relation(
HistoryDatasetAssociation,
- primaryjoin=( ( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ) & ( HistoryDatasetAssociation.table.c.visible == True ) ) )
+ primaryjoin=( ( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ) & ( HistoryDatasetAssociation.table.c.visible == True ) ) ),
+ tags=relation(HistoryDatasetAssociationTagAssociation, backref='history_tag_associations')
) )
assign_mapper( context, Dataset, Dataset.table,
@@ -659,7 +688,8 @@
primaryjoin=( Dataset.table.c.id == LibraryDatasetDatasetAssociation.table.c.dataset_id ) ),
active_library_associations=relation(
LibraryDatasetDatasetAssociation,
- primaryjoin=( ( Dataset.table.c.id == LibraryDatasetDatasetAssociation.table.c.dataset_id ) & ( LibraryDatasetDatasetAssociation.table.c.deleted == False ) ) )
+ primaryjoin=( ( Dataset.table.c.id == LibraryDatasetDatasetAssociation.table.c.dataset_id ) & ( LibraryDatasetDatasetAssociation.table.c.deleted == False ) ) ),
+ tags=relation(DatasetTagAssociation, backref='datasets')
) )
assign_mapper( context, HistoryDatasetAssociationDisplayAtAuthorization, HistoryDatasetAssociationDisplayAtAuthorization.table,
@@ -678,7 +708,8 @@
assign_mapper( context, History, History.table,
properties=dict( galaxy_sessions=relation( GalaxySessionToHistoryAssociation ),
datasets=relation( HistoryDatasetAssociation, backref="history", order_by=asc(HistoryDatasetAssociation.table.c.hid) ),
- active_datasets=relation( HistoryDatasetAssociation, primaryjoin=( ( HistoryDatasetAssociation.table.c.history_id == History.table.c.id ) & ( not_( HistoryDatasetAssociation.table.c.deleted ) ) ), order_by=asc( HistoryDatasetAssociation.table.c.hid ), viewonly=True )
+ active_datasets=relation( HistoryDatasetAssociation, primaryjoin=( ( HistoryDatasetAssociation.table.c.history_id == History.table.c.id ) & ( not_( HistoryDatasetAssociation.table.c.deleted ) ) ), order_by=asc( HistoryDatasetAssociation.table.c.hid ), viewonly=True ),
+ tags=relation(HistoryTagAssociation, backref="histories")
) )
assign_mapper( context, HistoryUserShareAssociation, HistoryUserShareAssociation.table,
@@ -938,6 +969,25 @@
lazy=False )
) )
+assign_mapper( context, Tag, Tag.table,
+ properties=dict( children=relation(Tag, backref=backref( 'parent', remote_side=[Tag.table.c.id] ) )
+ ) )
+
+assign_mapper( context, HistoryTagAssociation, HistoryTagAssociation.table,
+ properties=dict( tag=relation(Tag, backref="tagged_histories") ),
+ primary_key=[HistoryTagAssociation.table.c.history_id, HistoryTagAssociation.table.c.tag_id]
+ )
+
+assign_mapper( context, DatasetTagAssociation, DatasetTagAssociation.table,
+ properties=dict( tag=relation(Tag, backref="tagged_datasets") ),
+ primary_key=[DatasetTagAssociation.table.c.dataset_id, DatasetTagAssociation.table.c.tag_id]
+ )
+
+assign_mapper( context, HistoryDatasetAssociationTagAssociation, HistoryDatasetAssociationTagAssociation.table,
+ properties=dict( tag=relation(Tag, backref="tagged_history_dataset_associations") ),
+ primary_key=[HistoryDatasetAssociationTagAssociation.table.c.history_dataset_association_id, HistoryDatasetAssociationTagAssociation.table.c.tag_id]
+ )
+
def db_next_hid( self ):
"""
Override __next_hid to generate from the database in a concurrency
diff -r cc4944a62b66 -r fba947d16fa7 lib/galaxy/tags/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/tags/__init__.py Fri Aug 28 18:03:28 2009 -0400
@@ -0,0 +1,3 @@
+"""
+Galaxy tagging classes and methods.
+"""
diff -r cc4944a62b66 -r fba947d16fa7 lib/galaxy/tags/tag_handler.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/tags/tag_handler.py Fri Aug 28 18:03:28 2009 -0400
@@ -0,0 +1,226 @@
+from galaxy.model import Tag, History, HistoryTagAssociation, Dataset, DatasetTagAssociation, HistoryDatasetAssociation, HistoryDatasetAssociationTagAssociation
+import re
+
+class TagHandler( object ):
+
+ # Tag separator.
+ tag_separators = ',;'
+
+ # Hierarchy separator.
+ hierarchy_separator = '.'
+
+ # Key-value separator.
+ key_value_separators = "=:"
+
+ def __init__(self):
+ self.tag_assoc_classes = dict()
+
+ def add_tag_assoc_class(self, entity_class, tag_assoc_class):
+ self.tag_assoc_classes[entity_class] = tag_assoc_class
+
+ def get_tag_assoc_class(self, entity_class):
+ return self.tag_assoc_classes[entity_class]
+
+ # Remove a tag from an item.
+ def remove_item_tag(self, item, tag_name):
+ # Get item tag association.
+ item_tag_assoc = self._get_item_tag_assoc(item, tag_name)
+
+ # Remove association.
+ if item_tag_assoc:
+ # Delete association.
+ item_tag_assoc.delete()
+ item.tags.remove(item_tag_assoc)
+ return True
+
+ return False
+
+ # Delete tags from an item.
+ def delete_item_tags(self, item):
+ # Delete item-tag associations.
+ for tag in item.tags:
+ tag.delete()
+
+ # Delete tags from item.
+ del item.tags[:]
+
+ # Returns true if item is has a given tag.
+ def item_has_tag(self, item, tag_name):
+ # Check for an item-tag association to see if item has a given tag.
+ item_tag_assoc = self._get_item_tag_assoc(item, tag_name)
+ if item_tag_assoc:
+ return True
+ return False
+
+
+ # Apply tags to an item.
+ def apply_item_tags(self, db_session, item, tags_str):
+ # Parse tags.
+ parsed_tags = self._parse_tags(tags_str)
+
+ # Apply each tag.
+ for name, value in parsed_tags.items():
+ # Get or create item-tag association.
+ item_tag_assoc = self._get_item_tag_assoc(item, name)
+ if not item_tag_assoc:
+ #
+ # Create item-tag association.
+ #
+
+ # Create tag; if None, skip the tag (and log error).
+ tag = self._get_or_create_tag(db_session, name)
+ if not tag:
+ # Log error?
+ continue
+
+ # Create tag association based on item class.
+ item_tag_assoc_class = self.tag_assoc_classes[item.__class__]
+ item_tag_assoc = item_tag_assoc_class()
+
+ # Add tag to association.
+ item.tags.append(item_tag_assoc)
+ item_tag_assoc.tag = tag
+
+ # Apply attributes to item-tag association. Strip whitespace from user name and tag.
+ if value:
+ trimmed_value = value.strip()
+ else:
+ trimmed_value = value
+ item_tag_assoc.user_tname = name.strip()
+ item_tag_assoc.user_value = trimmed_value
+ item_tag_assoc.value = self._scrub_tag_value(value)
+
+ # Build a string from an item's tags.
+ def get_tags_str(self, tags):
+ # Return empty string if there are no tags.
+ if not tags:
+ return ""
+
+ # Create string of tags.
+ tags_str_list = list()
+ for tag in tags:
+ tag_str = tag.user_tname
+ if tag.value is not None:
+ tag_str += ":" + tag.user_value
+ tags_str_list.append(tag_str)
+ return ", ".join(tags_str_list)
+
+ # Get a Tag object from a tag string.
+ def _get_tag(self, db_session, tag_str):
+ return db_session.query(Tag).filter(Tag.name==tag_str).first()
+
+ # Create a Tag object from a tag string.
+ def _create_tag(self, db_session, tag_str):
+ tag_hierarchy = tag_str.split(self.__class__.hierarchy_separator)
+ tag_prefix = ""
+ parent_tag = None
+ for sub_tag in tag_hierarchy:
+ # Get or create subtag.
+ tag_name = tag_prefix + self._scrub_tag_name(sub_tag)
+ tag = db_session.query(Tag).filter(Tag.name==tag_name).first()
+ if not tag:
+ tag = Tag(type="generic", name=tag_name)
+
+ # Set tag parent.
+ tag.parent = parent_tag
+
+ # Update parent and tag prefix.
+ parent_tag = tag
+ tag_prefix = tag.name + self.__class__.hierarchy_separator
+ return tag
+
+ # Get or create a Tag object from a tag string.
+ def _get_or_create_tag(self, db_session, tag_str):
+ # Scrub tag; if tag is None after being scrubbed, return None.
+ scrubbed_tag_str = self._scrub_tag_name(tag_str)
+ if not scrubbed_tag_str:
+ return None
+
+ # Get item tag.
+ tag = self._get_tag(db_session, scrubbed_tag_str)
+
+ # Create tag if necessary.
+ if tag is None:
+ tag = self._create_tag(db_session, scrubbed_tag_str)
+
+ return tag
+
+ # Return ItemTagAssociation object for an item and a tag string; returns None if there is
+ # no such tag.
+ def _get_item_tag_assoc(self, item, tag_name):
+ scrubbed_tag_name = self._scrub_tag_name(tag_name)
+ for item_tag_assoc in item.tags:
+ if item_tag_assoc.tag.name == scrubbed_tag_name:
+ return item_tag_assoc
+ return None
+
+ # Returns a list of raw (tag-name, value) pairs derived from a string; method does not scrub tags.
+ # Return value is a dictionary where tag-names are keys.
+ def _parse_tags(self, tag_str):
+ # Gracefully handle None.
+ if not tag_str:
+ return dict()
+
+ # Split tags based on separators.
+ reg_exp = re.compile('[' + self.__class__.tag_separators + ']')
+ raw_tags = reg_exp.split(tag_str)
+
+ # Extract name-value pairs.
+ name_value_pairs = dict()
+ for raw_tag in raw_tags:
+ nv_pair = self._get_name_value_pair(raw_tag)
+ name_value_pairs[nv_pair[0]] = nv_pair[1]
+ return name_value_pairs
+
+ # Scrub a tag value.
+ def _scrub_tag_value(self, value):
+ # Gracefully handle None:
+ if not value:
+ return None
+
+ # Remove whitespace from value.
+ reg_exp = re.compile('\s')
+ scrubbed_value = re.sub(reg_exp, "", value)
+
+ # Lowercase and return.
+ return scrubbed_value.lower()
+
+ # Scrub a tag name.
+ def _scrub_tag_name(self, name):
+ # Gracefully handle None:
+ if not name:
+ return None
+
+ # Remove whitespace from name.
+ reg_exp = re.compile('\s')
+ scrubbed_name = re.sub(reg_exp, "", name)
+
+ # Ignore starting ':' char.
+ if scrubbed_name.startswith(self.__class__.hierarchy_separator):
+ scrubbed_name = scrubbed_name[1:]
+
+ # If name is too short or too long, return None.
+ if len(scrubbed_name) < 3 or len(scrubbed_name) > 255:
+ return None
+
+ # Lowercase and return.
+ return scrubbed_name.lower()
+
+ # Scrub a tag name list.
+ def _scrub_tag_name_list(self, tag_name_list):
+ scrubbed_tag_list = list()
+ for tag in tag_name_list:
+ scrubbed_tag_list.append(self._scrub_tag_name(tag))
+ return scrubbed_tag_list
+
+ # Get name, value pair from a tag string.
+ def _get_name_value_pair(self, tag_str):
+ # Use regular expression to parse name, value.
+ reg_exp = re.compile("[" + self.__class__.key_value_separators + "]")
+ name_value_pair = reg_exp.split(tag_str)
+
+ # Add empty slot if tag does not have value.
+ if len(name_value_pair) < 2:
+ name_value_pair.append(None)
+
+ return name_value_pair
\ No newline at end of file
diff -r cc4944a62b66 -r fba947d16fa7 lib/galaxy/web/controllers/tag.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/web/controllers/tag.py Fri Aug 28 18:03:28 2009 -0400
@@ -0,0 +1,147 @@
+"""
+Tags Controller: handles tagging/untagging of entities and provides autocomplete support.
+"""
+
+from galaxy.web.base.controller import *
+from galaxy.tags.tag_handler import *
+from sqlalchemy.sql.expression import func, and_
+from sqlalchemy.sql import select
+
+class TagsController ( BaseController ):
+
+ def __init__(self, app):
+ BaseController.__init__(self, app)
+
+ # Set up dict for mapping from short-hand to full item class.
+ self.shorthand_to_item_class_dict = dict()
+ self.shorthand_to_item_class_dict["history"] = History
+ self.shorthand_to_item_class_dict["hda"] = HistoryDatasetAssociation
+
+ # Set up tag handler to recognize the following items: History, HistoryDatasetAssociation, ...
+ self.tag_handler = TagHandler()
+ self.tag_handler.add_tag_assoc_class(History, HistoryTagAssociation)
+ self.tag_handler.add_tag_assoc_class(HistoryDatasetAssociation, HistoryDatasetAssociationTagAssociation)
+
+ @web.expose
+ def add_tag_async( self, trans, id=None, item_type=None, new_tag=None ):
+ """ Add tag to an item. """
+ item = self._get_item(trans, item_type, trans.security.decode_id(id))
+
+ self._do_security_check(trans, item)
+
+ self.tag_handler.apply_item_tags(trans.sa_session, item, new_tag)
+ trans.sa_session.flush()
+
+ @web.expose
+ def remove_tag_async( self, trans, id=None, item_type=None, tag_name=None ):
+ """ Remove tag from an item. """
+ item = self._get_item(trans, item_type, trans.security.decode_id(id))
+
+ self._do_security_check(trans, item)
+
+ self.tag_handler.remove_item_tag(item, tag_name)
+ trans.sa_session.flush()
+
+ # Retag an item. All previous tags are deleted and new tags are applied.
+ @web.expose
+ def retag_async( self, trans, id=None, item_type=None, new_tags=None ):
+ """ Apply a new set of tags to an item; previous tags are deleted. """
+ item = self._get_item(trans, item_type, trans.security.decode_id(id))
+
+ self._do_security_check(trans, item)
+
+ tag_handler.delete_item_tags(item)
+ self.tag_handler.apply_item_tags(trans.sa_session, item, new_tag)
+ trans.sa_session.flush()
+
+ tag_handler.delete_item_tags(history)
+ tag_handler.apply_item_tags(trans.sa_session, history, new_tags)
+ # Flush to complete changes.
+ trans.sa_session.flush()
+
+ @web.expose
+ @web.require_login( "get autocomplete data for an item's tags" )
+ def tag_autocomplete_data(self, trans, id=None, item_type=None, q=None, limit=None, timestamp=None):
+ """ Get autocomplete data for an item's tags. """
+ item = self._get_item(trans, item_type, trans.security.decode_id(id))
+
+ self._do_security_check(trans, item)
+
+ #
+ # Get user's item tags and usage counts.
+ #
+
+ # Get item-tag association class.
+ item_tag_assoc_class = self.tag_handler.get_tag_assoc_class(item.__class__)
+
+ # Build select statement.
+ cols_to_select = [ item_tag_assoc_class.table.c.tag_id, item_tag_assoc_class.table.c.user_tname, item_tag_assoc_class.table.c.user_value, func.count('*') ]
+ from_obj = item_tag_assoc_class.table.join(item.table).join(Tag)
+ where_clause = self._get_column_for_filtering_item_by_user_id(item)==trans.get_user().id
+ order_by = [ func.count("*").desc() ]
+ ac_for_names = not q.endswith(":")
+ if ac_for_names:
+ # Autocomplete for tag names.
+ where_clause = and_(where_clause, Tag.table.c.name.like(q + "%"))
+ group_by = item_tag_assoc_class.table.c.tag_id
+ else:
+ # Autocomplete for tag values.
+ tag_name_and_value = q.split(":")
+ tag_name = tag_name_and_value[0]
+ tag_value = tag_name_and_value[1]
+ where_clause = and_(where_clause, Tag.table.c.name==tag_name)
+ where_clause = and_(where_clause, item_tag_assoc_class.table.c.value.like(tag_value + "%"))
+ group_by = item_tag_assoc_class.table.c.value
+
+ # Do query and get result set.
+ query = select(columns=cols_to_select, from_obj=from_obj,
+ whereclause=where_clause, group_by=group_by, order_by=order_by)
+ result_set = trans.sa_session.execute(query)
+
+ # Create and return autocomplete data.
+ if ac_for_names:
+ # Autocomplete for tag names.
+ ac_data = "#Header|Your Tags\n"
+ for row in result_set:
+ # Exclude tags that are already applied to the history.
+ if self.tag_handler.item_has_tag(item, row[1]):
+ continue
+ # Add tag to autocomplete data.
+ ac_data += row[1] + "|" + row[1] + "\n"
+ else:
+ # Autocomplete for tag values.
+ ac_data = "#Header|Your Values for '%s'\n" % (tag_name)
+ for row in result_set:
+ ac_data += tag_name + ":" + row[2] + "|" + row[2] + "\n"
+
+ return ac_data
+
+ def _get_column_for_filtering_item_by_user_id(self, item):
+ """ Returns the column to use when filtering by user id. """
+ if isinstance(item, History):
+ return item.table.c.user_id
+ elif isinstance(item, HistoryDatasetAssociation):
+ # Use the user_id associated with the HDA's history.
+ history = item.history
+ return history.table.c.user_id
+
+ def _get_item(self, trans, item_type, id):
+ """ Get an item based on type and id. """
+ item_class = self.shorthand_to_item_class_dict[item_type]
+ item = trans.sa_session.query(item_class).filter("id=" + str(id))[0]
+ return item;
+
+ def _do_security_check(self, trans, item):
+ """ Do security check on an item. """
+ if isinstance(item, History):
+ history = item;
+ # Check that the history exists, and is either owned by the current
+ # user (if logged in) or the current history
+ assert history is not None
+ if history.user is None:
+ assert history == trans.get_history()
+ else:
+ assert history.user == trans.user
+ elif isinstance(item, HistoryDatasetAssociation):
+ # TODO.
+ pass
diff -r cc4944a62b66 -r fba947d16fa7 static/june_2007_style/autocomplete_tagging.css.tmpl
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/june_2007_style/autocomplete_tagging.css.tmpl Fri Aug 28 18:03:28 2009 -0400
@@ -0,0 +1,133 @@
+/****************************************************************************/
+/* JQuery autocomplete code */
+/****************************************************************************/
+
+.ac_results {
+ padding: 0px;
+ border: 1px solid black;
+ background-color: white;
+ overflow: hidden;
+ z-index: 99999;
+}
+
+.ac_results ul {
+ width: 100%;
+ list-style-position: outside;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.ac_results li {
+ padding: 2px 5px;
+ cursor: default;
+ display: block;
+ /*
+ if width will be 100% horizontal scrollbar will apear
+ when scroll mode will be used
+ */
+ /*width: 100%;*/
+ /* font: menu; */
+ font-size: 12px;
+ /*
+ it is very important, if line-height not setted or setted
+ in relative units scroll will be broken in firefox
+ */
+ line-height: 16px;
+ overflow: hidden;
+}
+
+.ac_loading {
+ background: white url('indicator.gif') right center no-repeat;
+}
+
+.ac_odd {
+ background-color: #fff; /* #eee */
+ margin-left: 0.3em;
+}
+
+.ac_even {
+ margin-left: 0.3em;
+}
+
+.ac_over {
+ background-color: #0A246A;
+ color: white;
+}
+
+.ac_header {
+ font-style: normal;
+ color: gray;
+ border-bottom: 0.1em solid gray;
+}
+
+/****************************************************************************/
+/* Custom code for supporting tags */
+/****************************************************************************/
+.tag-area {
+ width: 100%;
+ cursor: pointer;
+ border: solid 1px #eee;
+}
+
+.active-tag-area {
+ background-color: white;
+}
+
+.toggle-link
+{
+ font-weight: bold;
+ padding: 0.3em;
+ margin-bottom: 1em;
+ width: 100%;
+ padding: 0.2em 0em 0.2em 0em;
+}
+
+.tag-button {
+ width: auto;
+ color: #444;
+ text-decoration: none;
+ display: inline-block;
+ cursor: pointer;
+ margin: 0.2em;
+ border: 0;
+ padding: 0.1em 0.5em 0.1em 0.5em;
+ -moz-border-radius: .5em;
+ -webkit-border-radius: .5em;
+ border-radius: .5em;
+ background:#bbb;
+}
+
+.tag-button img
+{
+ padding-left: 0.4em;
+}
+
+.tag-button .tag-name:hover
+{
+ color: white;
+}
+
+.add-tag-button
+{
+ margin-bottom: 0.3em;
+ vertical-align: middle;
+ padding: 0.3em;
+}
+
+.add-tag-button:hover
+{
+ cursor: pointer;
+}
+
+.tag-input {
+ vertical-align: bottom;
+ border: none;
+ outline: none;
+}
+
+.delete-tag-img
+{
+
+ margin-left: 0.3em
+}
\ No newline at end of file
diff -r cc4944a62b66 -r fba947d16fa7 static/june_2007_style/blue/autocomplete_tagging.css
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/june_2007_style/blue/autocomplete_tagging.css Fri Aug 28 18:03:28 2009 -0400
@@ -0,0 +1,133 @@
+/****************************************************************************/
+/* JQuery autocomplete code */
+/****************************************************************************/
+
+.ac_results {
+ padding: 0px;
+ border: 1px solid black;
+ background-color: white;
+ overflow: hidden;
+ z-index: 99999;
+}
+
+.ac_results ul {
+ width: 100%;
+ list-style-position: outside;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.ac_results li {
+ padding: 2px 5px;
+ cursor: default;
+ display: block;
+ /*
+ if width will be 100% horizontal scrollbar will apear
+ when scroll mode will be used
+ */
+ /*width: 100%;*/
+ /* font: menu; */
+ font-size: 12px;
+ /*
+ it is very important, if line-height not setted or setted
+ in relative units scroll will be broken in firefox
+ */
+ line-height: 16px;
+ overflow: hidden;
+}
+
+.ac_loading {
+ background: white url('indicator.gif') right center no-repeat;
+}
+
+.ac_odd {
+ background-color: #fff; /* #eee */
+ margin-left: 0.3em;
+}
+
+.ac_even {
+ margin-left: 0.3em;
+}
+
+.ac_over {
+ background-color: #0A246A;
+ color: white;
+}
+
+.ac_header {
+ font-style: normal;
+ color: gray;
+ border-bottom: 0.1em solid gray;
+}
+
+/****************************************************************************/
+/* Custom code for supporting tags */
+/****************************************************************************/
+.tag-area {
+ width: 100%;
+ cursor: pointer;
+ border: solid 1px #eee;
+}
+
+.active-tag-area {
+ background-color: white;
+}
+
+.toggle-link
+{
+ font-weight: bold;
+ padding: 0.3em;
+ margin-bottom: 1em;
+ width: 100%;
+ padding: 0.2em 0em 0.2em 0em;
+}
+
+.tag-button {
+ width: auto;
+ color: #444;
+ text-decoration: none;
+ display: inline-block;
+ cursor: pointer;
+ margin: 0.2em;
+ border: 0;
+ padding: 0.1em 0.5em 0.1em 0.5em;
+ -moz-border-radius: .5em;
+ -webkit-border-radius: .5em;
+ border-radius: .5em;
+ background:#bbb;
+}
+
+.tag-button img
+{
+ padding-left: 0.4em;
+}
+
+.tag-button .tag-name:hover
+{
+ color: white;
+}
+
+.add-tag-button
+{
+ margin-bottom: 0.3em;
+ vertical-align: middle;
+ padding: 0.3em;
+}
+
+.add-tag-button:hover
+{
+ cursor: pointer;
+}
+
+.tag-input {
+ vertical-align: bottom;
+ border: none;
+ outline: none;
+}
+
+.delete-tag-img
+{
+
+ margin-left: 0.3em
+}
\ No newline at end of file
diff -r cc4944a62b66 -r fba947d16fa7 static/june_2007_style/make_style.py
--- a/static/june_2007_style/make_style.py Fri Aug 28 16:52:58 2009 -0400
+++ b/static/june_2007_style/make_style.py Fri Aug 28 18:03:28 2009 -0400
@@ -27,7 +27,8 @@
( "history.css.tmpl", "history.css" ),
( "tool_menu.css.tmpl", "tool_menu.css" ),
( "iphone.css.tmpl", "iphone.css" ),
- ( "reset.css.tmpl", "reset.css" ) ]
+ ( "reset.css.tmpl", "reset.css" ),
+ ( "autocomplete_tagging.css.tmpl", "autocomplete_tagging.css") ]
images = [
( "./gradient.py 9 30 $panel_header_bg_top - $panel_header_bg_bottom 0 0 $panel_header_bg_bottom 1 1", "panel_header_bg.png" ),
diff -r cc4944a62b66 -r fba947d16fa7 static/scripts/autocomplete_tagging.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/scripts/autocomplete_tagging.js Fri Aug 28 18:03:28 2009 -0400
@@ -0,0 +1,453 @@
+/**
+ * JQuery extension for tagging with autocomplete.
+ * @author: Jeremy Goecks
+ * @require: jquery.autocomplete plugin
+ */
+var ac_tag_area_id_gen = 1;
+
+jQuery.fn.autocomplete_tagging = function(options) {
+
+ //
+ // Set up function defaults.
+ //
+ var defaults =
+ {
+ get_toggle_link_text_fn: function(tags)
+ {
+ var text = "";
+ var num_tags = array_length(tags);
+ if (num_tags != 0)
+ text = num_tags + (num_tags != 0 ? " Tags" : " Tag");
+ else
+ // No tags.
+ text = "Add tags";
+ return text;
+ },
+ tag_click_fn : function (tag) { },
+ input_size: 20,
+ in_form: false,
+ tags : {},
+ use_toggle_link: true,
+ item_id: "",
+ add_tag_img: "",
+ add_tag_img_rollover: "",
+ delete_tag_img: "",
+ ajax_autocomplete_tag_url: "",
+ ajax_retag_url: "",
+ ajax_delete_tag_url: "",
+ ajax_add_tag_url: ""
+ };
+
+ //
+ // Extend object.
+ //
+ var settings = jQuery.extend(defaults, options);
+
+ //
+ // Create core elements: tag area and TODO.
+ //
+
+ // Tag area.
+ var area_id = "tag-area-" + (ac_tag_area_id_gen)++;
+ var tag_area = $("<div></div>").attr("id", area_id).addClass("tag-area");
+ this.append(tag_area);
+
+ //
+ // Returns the number of keys (elements) in an array/dictionary.
+ //
+ var array_length = function(an_array)
+ {
+ if (an_array.length)
+ return an_array.length;
+
+ var count = 0;
+ for (element in an_array)
+ count++;
+ return count;
+ };
+
+ //
+ // Function to build toggle link.
+ //
+ var build_toggle_link = function()
+ {
+ var link_text = settings.get_toggle_link_text_fn(settings.tags);
+ var toggle_link = $("<a href='/history/tags'>" + link_text + "</a>").addClass("toggle-link");
+ // Link toggles the display state of the tag area.
+ toggle_link.click( function()
+ {
+ // Take special actions depending on whether toggle is showing or hiding link.
+ var showing_tag_area = (tag_area.css("display") == "none");
+ var after_toggle_fn;
+ if (showing_tag_area)
+ {
+ after_toggle_fn = function()
+ {
+ // If there are no tags, go right to editing mode by generating a
+ // click on the area.
+ var num_tags = array_length(settings.tags);
+ if (num_tags == 0)
+ tag_area.click();
+ };
+ }
+ else // Hiding area.
+ {
+ after_toggle_fn = function()
+ {
+ tag_area.blur();
+ };
+ }
+ tag_area.slideToggle("fast", after_toggle_fn);
+
+ return false;
+ });
+
+ return toggle_link;
+ };
+
+ // Add toggle link.
+ var toggle_link = build_toggle_link();
+ if (settings.use_toggle_link)
+ {
+ this.prepend(toggle_link);
+ }
+
+ //
+ // Function to build other elements.
+ //
+
+ //
+ // Return a string that contains the contents of an associative array. This is
+ // a debugging method.
+ //
+ var assoc_array_to_str = function(an_array)
+ {
+ // Convert associative array to simple array and then join array elements.
+ var array_str_list = new Array();
+ for (key in an_array)
+ array_str_list[array_str_list.length] = key + "-->" + an_array[key];
+
+ return "{" + array_str_list.join(",") + "}"
+ };
+
+ //
+ // Collapse tag name + value into a single string.
+ //
+ var build_tag_str = function(tag_name, tag_value)
+ {
+ return tag_name + ( (tag_value != "" && tag_value) ? ":" + tag_value : "");
+ };
+
+ //
+ // Get tag name and value from a string.
+ //
+ var get_tag_name_and_value = function(tag_str)
+ {
+ return tag_str.split(":");
+ };
+
+ //
+ // Add "add tag" button.
+ //
+ var build_add_tag_button = function(tag_input_field)
+ {
+ var add_tag_button = $("<img src='" + settings.add_tag_img + "' rollover='" + settings.add_tag_img_rollover + "'/>").addClass("add-tag-button");
+
+ add_tag_button.click( function()
+ {
+ // Hide button.
+ $(this).hide();
+
+ // Clicking on button is the same as clicking on the tag area.
+ tag_area.click();
+
+ return false;
+ });
+
+ return add_tag_button;
+ };
+
+ //
+ // Function that builds a tag button.
+ //
+ var build_tag_button = function(tag_str)
+ {
+ // Build "delete tag" image and handler.
+ var delete_img = $("<img src='" + settings.delete_tag_img + "'/>").addClass("delete-tag-img");
+ delete_img.mouseenter( function ()
+ {
+ $(this).attr("src", settings.delete_tag_img_rollover);
+ });
+ delete_img.mouseleave( function ()
+ {
+ $(this).attr("src", settings.delete_tag_img);
+ });
+ delete_img.click( function ()
+ {
+ // Tag button is image's parent.
+ var tag_button = $(this).parent();
+
+ // Get tag name.
+ var tag_name_elt = tag_button.find(".tag-name").eq(0);
+ var tag_str = tag_name_elt.text();
+ var tag_name = get_tag_name_and_value(tag_str)[0];
+
+ // TODO: should remove succeed if tag is not already applied to
+ // history?
+ tag_button.remove();
+
+ // Remove tag from local list for consistency.
+ delete settings.tags[tag_name];
+
+ // Update toggle link text.
+ var new_text = settings.get_toggle_link_text_fn(settings.tags);
+ toggle_link.text(new_text);
+
+ // Delete tag.
+ $.ajax({
+ url: settings.ajax_delete_tag_url,
+ data: { tag_name: tag_name },
+ error: function()
+ {
+ // Failed.
+ alert( "Remove tag failed" );
+ },
+ success: function()
+ {
+ }
+ });
+
+ return true;
+ });
+
+ // Build tag button.
+ var tag_name_elt = $("<span>" + tag_str + "</span>").addClass("tag-name");
+ tag_name_elt.click( function()
+ {
+ settings.tag_click_fn(tag_str);
+ return true;
+ });
+
+ var tag_button = $("<span></span>").addClass("tag-button");
+ tag_button.append(tag_name_elt);
+ tag_button.append(delete_img);
+
+ return tag_button;
+ };
+
+ //
+ // Build input + autocompete for tag.
+ //
+ var build_tag_input = function(tag_text)
+ {
+ // If element is in form, tag input is a textarea; otherwise element is a input type=text.
+ var t;
+ if (settings.in_form)
+ t = $( "<textarea id='history-tag-input' rows='1' cols='" +
+ settings.input_size + "' value='" + tag_text + "'></textarea>" );
+ else // element not in form.
+ t = $( "<input id='history-tag-input' type='text' size='" +
+ settings.input_size + "' value='" + tag_text + "'></input>" );
+ t.keyup( function( e )
+ {
+ if ( e.keyCode == 27 )
+ {
+ // Escape key
+ $(this).trigger( "blur" );
+ } else if (
+ ( e.keyCode == 13 ) || // Return Key
+ ( e.keyCode == 188 ) || // Comma
+ ( e.keyCode == 32 ) // Space
+ )
+ {
+ //
+ // Check input.
+ //
+
+ new_value = this.value;
+
+ // Do nothing if return key was used to autocomplete.
+ if (return_key_pressed_for_autocomplete == true)
+ {
+ return_key_pressed_for_autocomplete = false;
+ return false;
+ }
+
+ // Suppress space after a ":"
+ if ( new_value.indexOf(": ", new_value.length - 2) != -1)
+ {
+ this.value = new_value.substring(0, new_value.length-1);
+ return false;
+ }
+
+ // Remove trigger keys from input.
+ if ( (e.keyCode == 188) || (e.keyCode == 32) )
+ new_value = new_value.substring( 0 , new_value.length - 1 );
+
+ // Trim whitespace.
+ new_value = new_value.replace(/^\s+|\s+$/g,"");
+
+ // Too short?
+ if (new_value.length < 3)
+ return false;
+
+ //
+ // New tag OK - apply it.
+ //
+
+ this.value = "";
+
+ // Add button for tag after all other tag buttons.
+ var new_tag_button = build_tag_button(new_value);
+ var tag_buttons = tag_area.children(".tag-button");
+ if (tag_buttons.length != 0)
+ {
+ var last_tag_button = tag_buttons.slice(tag_buttons.length-1);
+ last_tag_button.after(new_tag_button);
+ }
+ else
+ tag_area.prepend(new_tag_button);
+
+ // Add tag to internal list.
+ var tag_name_and_value = new_value.split(":");
+ settings.tags[tag_name_and_value[0]] = tag_name_and_value[1];
+
+ // Update toggle link text.
+ var new_text = settings.get_toggle_link_text_fn(settings.tags);
+ toggle_link.text(new_text);
+
+ // Commit tag to server.
+ var $this = $(this);
+ $.ajax({
+ url: settings.ajax_add_tag_url,
+ data: { new_tag: new_value },
+ error: function()
+ {
+ // Remove tag and show alert.
+ new_tag_button.remove();
+ var new_text = settings.get_toggle_link_text_fn(settings.tags);
+ toggle_link.text(new_text);
+ alert( "Add tag failed" );
+ },
+ success: function()
+ {
+ // Flush autocomplete cache because it's not out of date.
+ // TODO: in the future, we could remove the particular item
+ // that was chosen from the cache rather than flush it.
+ $this.flushCache();
+ }
+ });
+
+ return false;
+ }
+ });
+
+ // Add autocomplete to input.
+ var format_item_func = function(key, row_position, num_rows, value, search_term) {
+ tag_name_and_value = value.split(":");
+ return (tag_name_and_value.length == 1 ? tag_name_and_value[0] :tag_name_and_value[1]);
+ //var array = new Array(key, value, row_position, num_rows,
+ //search_term ); return "\"" + array.join("*") + "\"";
+ }
+ var autocomplete_options =
+ { selectFirst: false, formatItem : format_item_func, autoFill: false, highlight: false };
+
+ t.autocomplete(settings.ajax_autocomplete_tag_url, autocomplete_options);
+
+ t.addClass("tag-input");
+
+ return t;
+ };
+
+ //
+ // Build tag area.
+ //
+
+ // Add tag buttons for each current tag to the tag area.
+ for (tag_name in settings.tags)
+ {
+ var tag_value = settings.tags[tag_name];
+ var tag_str = build_tag_str(tag_name, tag_value);
+ var tag_button = build_tag_button(tag_str, toggle_link, settings.tags);
+ tag_area.append(tag_button);
+ }
+
+ // Add tag input field and "add tag" button.
+ var tag_input_field = build_tag_input("");
+ var add_tag_button = build_add_tag_button(tag_input_field);
+
+ // When the tag area blurs, go to "view tag" mode.
+ tag_area.blur( function(e)
+ {
+ add_tag_button.show();
+ tag_input_field.hide();
+ tag_area.removeClass("active-tag-area");
+ });
+
+ tag_area.append(add_tag_button);
+ tag_area.append(tag_input_field);
+ tag_input_field.hide();
+
+ // On click, enable user to add tags.
+ tag_area.click( function(e)
+ {
+ var is_active = $(this).hasClass("active-tag-area");
+
+ // If a "delete image" object was pressed and area is inactive, do nothing.
+ if ($(e.target).hasClass("delete-tag-img") && !is_active)
+ return false;
+
+ // If a "tag name" object was pressed and area is inactive, do nothing.
+ if ($(e.target).hasClass("tag-name") && !is_active)
+ return false;
+
+ // Hide add tag button, show tag_input field. Change background to show
+ // area is active.
+ $(this).addClass("active-tag-area");
+ add_tag_button.hide();
+ tag_input_field.show();
+ tag_input_field.focus();
+
+ // Add handler to document that will call blur when the tag area is blurred;
+ // a tag area is blurred when a user clicks on an element outside the area.
+ var handle_document_click = function(e)
+ {
+ var tag_area_id = tag_area.attr("id");
+ // Blur the tag area if the element clicked on is not in the tag area.
+ if (
+ ($(e.target).attr("id") != tag_area_id) &&
+ ($(e.target).parents().filter(tag_area_id).length == 0)
+ )
+ {
+ tag_area.blur();
+ $(document).unbind("click", handle_document_click);
+ }
+ };
+ // TODO: we should attach the click handler to all frames in order to capture
+ // clicks outside the frame that this element is in.
+ //window.parent.document.onclick = handle_document_click;
+ //var temp = $(window.parent.document.body).contents().find("iframe").html();
+ //alert(temp);
+ //$(document).parent().click(handle_document_click);
+ $(window).click(handle_document_click);
+
+ return false;
+ });
+
+ // If using toggle link, hide the tag area. Otherwise, if there are no tags,
+ // hide the "add tags" button and show the input field.
+ if (settings.use_toggle_link)
+ tag_area.hide();
+ else
+ {
+ var num_tags = array_length(settings.tags);
+ if (num_tags == 0)
+ {
+ add_tag_button.hide();
+ tag_input_field.show();
+ }
+ }
+
+
+ return this.addClass("tag-element");
+}
diff -r cc4944a62b66 -r fba947d16fa7 static/scripts/jquery.autocomplete.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/scripts/jquery.autocomplete.js Fri Aug 28 18:03:28 2009 -0400
@@ -0,0 +1,824 @@
+/*
+ * Autocomplete - jQuery plugin 1.0.2
+ *
+ * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
+ *
+ */
+
+String.prototype.endsWith = function(str) {return (this.match(str+"$")==str)}
+
+
+var return_key_pressed_for_autocomplete = false;
+
+;(function($) {
+
+$.fn.extend({
+ autocomplete: function(urlOrData, options) {
+ var isUrl = typeof urlOrData == "string";
+ options = $.extend({}, $.Autocompleter.defaults, {
+ url: isUrl ? urlOrData : null,
+ data: isUrl ? null : urlOrData,
+ delay: isUrl ? $.Autocompleter.defaults.delay : 10,
+ max: options && !options.scroll ? 10 : 150
+ }, options);
+
+ // if highlight is set to false, replace it with a do-nothing function
+ options.highlight = options.highlight || function(value) { return value; };
+
+ // if the formatMatch option is not specified, then use formatItem for backwards compatibility
+ options.formatMatch = options.formatMatch || options.formatItem;
+
+ return this.each(function() {
+ new $.Autocompleter(this, options);
+ });
+ },
+ result: function(handler) {
+ return this.bind("result", handler);
+ },
+ search: function(handler) {
+ return this.trigger("search", [handler]);
+ },
+ flushCache: function() {
+ return this.trigger("flushCache");
+ },
+ setOptions: function(options){
+ return this.trigger("setOptions", [options]);
+ },
+ unautocomplete: function() {
+ return this.trigger("unautocomplete");
+ }
+});
+
+$.Autocompleter = function(input, options) {
+
+ var KEY = {
+ UP: 38,
+ DOWN: 40,
+ DEL: 46,
+ TAB: 9,
+ RETURN: 13,
+ ESC: 27,
+ COMMA: 188,
+ PAGEUP: 33,
+ PAGEDOWN: 34,
+ BACKSPACE: 8,
+ COLON: 16
+ };
+
+ // Create $ object for input element
+ var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
+
+ var timeout;
+ var previousValue = "";
+ var cache = $.Autocompleter.Cache(options);
+ var hasFocus = 0;
+ var lastKeyPressCode;
+ var config = {
+ mouseDownOnSelect: false
+ };
+ var select = $.Autocompleter.Select(options, input, selectCurrent, config);
+
+ var blockSubmit;
+
+ // prevent form submit in opera when selecting with return key
+ $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
+ if (blockSubmit) {
+ blockSubmit = false;
+ return false;
+ }
+ });
+
+ // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
+ $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
+ // track last key pressed
+ lastKeyPressCode = event.keyCode;
+ switch(event.keyCode) {
+
+ case KEY.UP:
+ event.preventDefault();
+ if ( select.visible() ) {
+ select.prev();
+ } else {
+ onChange(0, true);
+ }
+ break;
+
+ case KEY.DOWN:
+ event.preventDefault();
+ if ( select.visible() ) {
+ select.next();
+ } else {
+ onChange(0, true);
+ }
+ break;
+
+ case KEY.PAGEUP:
+ event.preventDefault();
+ if ( select.visible() ) {
+ select.pageUp();
+ } else {
+ onChange(0, true);
+ }
+ break;
+
+ case KEY.PAGEDOWN:
+ event.preventDefault();
+ if ( select.visible() ) {
+ select.pageDown();
+ } else {
+ onChange(0, true);
+ }
+ break;
+
+ // matches also semicolon
+ case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
+ case KEY.TAB:
+ case KEY.RETURN:
+ if (event.keyCode == KEY.RETURN)
+ return_key_pressed_for_autocomplete = false;
+ if( selectCurrent() ) {
+ // stop default to prevent a form submit, Opera needs special handling
+ event.preventDefault();
+ blockSubmit = true;
+
+ // JG: set flag to indicate that a selection just occurred using the return key. FYI:
+ // event.stopPropagation() does not work.
+ if (event.keyCode == KEY.RETURN)
+ return_key_pressed_for_autocomplete = true;
+
+ return false;
+ }
+
+ case KEY.ESC:
+ select.hide();
+ break;
+ case KEY.COLON:
+ break;
+
+ default:
+ clearTimeout(timeout);
+ timeout = setTimeout(onChange, options.delay);
+ break;
+ }
+ }).focus(function(){
+ // track whether the field has focus, we shouldn't process any
+ // results if the field no longer has focus
+ hasFocus++;
+ }).blur(function() {
+ hasFocus = 0;
+ if (!config.mouseDownOnSelect) {
+ // JG: if blur and user is not selecting with mouse, hide
+ // object.
+ select.hide();
+ }
+ return this;
+ }).click(function() {
+ // show select when clicking in a focused field
+ if ( hasFocus++ > 1 && !select.visible() ) {
+ onChange(0, true);
+ }
+ return this;
+ }).bind("search", function() {
+ // TODO why not just specifying both arguments?
+ var fn = (arguments.length > 1) ? arguments[1] : null;
+ function findValueCallback(q, data) {
+ var result;
+ if( data && data.length ) {
+ for (var i=0; i < data.length; i++) {
+ if( data[i].result.toLowerCase() == q.toLowerCase() ) {
+ result = data[i];
+ break;
+ }
+ }
+ }
+ if( typeof fn == "function" ) fn(result);
+ else $input.trigger("result", result && [result.data, result.value]);
+ }
+ $.each(trimWords($input.val()), function(i, value) {
+ request(value, findValueCallback, findValueCallback);
+ });
+
+ return this;
+ }).bind("flushCache", function() {
+ cache.flush();
+ }).bind("setOptions", function() {
+ $.extend(options, arguments[1]);
+ // if we've updated the data, repopulate
+ if ( "data" in arguments[1] )
+ cache.populate();
+ }).bind("unautocomplete", function() {
+ select.unbind();
+ $input.unbind();
+ $(input.form).unbind(".autocomplete");
+ });
+
+
+ function selectCurrent() {
+ var selected = select.selected();
+ if( !selected )
+ return false;
+
+ var v = selected.result;
+ previousValue = v;
+
+ if ( options.multiple ) {
+ var words = trimWords($input.val());
+ if ( words.length > 1 ) {
+ v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
+ }
+ v += options.multipleSeparator;
+ }
+
+ $input.val(v);
+ hideResultsNow();
+ $input.trigger("result", [selected.data, selected.value]);
+ return true;
+ }
+
+ function onChange(crap, skipPrevCheck) {
+ if( lastKeyPressCode == KEY.DEL ) {
+ select.hide();
+ return;
+ }
+
+ var currentValue = $input.val();
+
+ if ( !skipPrevCheck && currentValue == previousValue )
+ return;
+
+ previousValue = currentValue;
+
+ currentValue = lastWord(currentValue);
+ if ( currentValue.length >= options.minChars) {
+ $input.addClass(options.loadingClass);
+ if (!options.matchCase)
+ currentValue = currentValue.toLowerCase();
+ request(currentValue, receiveData, hideResultsNow);
+ } else {
+ stopLoading();
+ select.hide();
+ }
+ };
+
+ function trimWords(value) {
+ if ( !value ) {
+ return [""];
+ }
+ var words = value.split( options.multipleSeparator );
+ var result = [];
+ $.each(words, function(i, value) {
+ if ( $.trim(value) )
+ result[i] = $.trim(value);
+ });
+ return result;
+ }
+
+ function lastWord(value) {
+ if ( !options.multiple )
+ return value;
+ var words = trimWords(value);
+ return words[words.length - 1];
+ }
+
+ // fills in the input box w/the first match (assumed to be the best match)
+ // q: the term entered
+ // sValue: the first matching result
+ function autoFill(q, sValue){
+ // autofill in the complete box w/the first match as long as the user hasn't entered in more data
+ // if the last user key pressed was backspace, don't autofill
+ if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
+ // fill in the value (keep the case the user has typed)
+ $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
+ // select the portion of the value not typed by the user (so the next character will erase)
+ $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
+ }
+ };
+
+ function hideResults() {
+ clearTimeout(timeout);
+ timeout = setTimeout(hideResultsNow, 200);
+ };
+
+ function hideResultsNow() {
+ var wasVisible = select.visible();
+ select.hide();
+ clearTimeout(timeout);
+ stopLoading();
+ if (options.mustMatch) {
+ // call search and run callback
+ $input.search(
+ function (result){
+ // if no value found, clear the input box
+ if( !result ) {
+ if (options.multiple) {
+ var words = trimWords($input.val()).slice(0, -1);
+ $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
+ }
+ else
+ $input.val( "" );
+ }
+ }
+ );
+ }
+ if (wasVisible)
+ // position cursor at end of input field
+ $.Autocompleter.Selection(input, input.value.length, input.value.length);
+ };
+
+ function receiveData(q, data) {
+ if ( data && data.length && hasFocus ) {
+ stopLoading();
+ select.display(data, q);
+ autoFill(q, data[0].value);
+ select.show();
+ } else {
+ hideResultsNow();
+ }
+ };
+
+ function request(term, success, failure) {
+ if (!options.matchCase)
+ term = term.toLowerCase();
+ var data = cache.load(term);
+
+ // JG: hack: if term ends with ':', kill data to force an ajax request.
+ if (term.endsWith(":"))
+ data = null;
+
+ // recieve the cached data
+ if (data && data.length) {
+ success(term, data);
+ // if an AJAX url has been supplied, try loading the data now
+ } else if( (typeof options.url == "string") && (options.url.length > 0) ){
+ var extraParams = {
+ timestamp: +new Date()
+ };
+ $.each(options.extraParams, function(key, param) {
+ extraParams[key] = typeof param == "function" ? param() : param;
+ });
+
+ $.ajax({
+ // try to leverage ajaxQueue plugin to abort previous requests
+ mode: "abort",
+ // limit abortion to this input
+ port: "autocomplete" + input.name,
+ dataType: options.dataType,
+ url: options.url,
+ data: $.extend({
+ q: lastWord(term),
+ limit: options.max
+ }, extraParams),
+ success: function(data) {
+ var parsed = options.parse && options.parse(data) || parse(data);
+ cache.add(term, parsed);
+ success(term, parsed);
+ }
+ });
+ } else {
+ // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
+ select.emptyList();
+ failure(term);
+ }
+ };
+
+ function parse(data) {
+ var parsed = [];
+ var rows = data.split("\n");
+ for (var i=0; i < rows.length; i++) {
+ var row = $.trim(rows[i]);
+ if (row) {
+ row = row.split("|");
+ parsed[parsed.length] = {
+ data: row,
+ value: row[0],
+ result: options.formatResult && options.formatResult(row, row[0]) || row[0]
+ };
+ }
+ }
+ return parsed;
+ };
+
+ function stopLoading() {
+ $input.removeClass(options.loadingClass);
+ };
+
+};
+
+$.Autocompleter.defaults = {
+ inputClass: "ac_input",
+ resultsClass: "ac_results",
+ loadingClass: "ac_loading",
+ minChars: 1,
+ delay: 400,
+ matchCase: false,
+ matchSubset: true,
+ matchContains: false,
+ cacheLength: 10,
+ max: 100,
+ mustMatch: false,
+ extraParams: {},
+ selectFirst: true,
+ formatItem: function(row) { return row[0]; },
+ formatMatch: null,
+ autoFill: false,
+ width: 0,
+ multiple: false,
+ multipleSeparator: ", ",
+ highlight: function(value, term) {
+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
+ },
+ scroll: true,
+ scrollHeight: 180
+};
+
+$.Autocompleter.Cache = function(options) {
+
+ var data = {};
+ var length = 0;
+
+ function matchSubset(s, sub) {
+ if (!options.matchCase)
+ s = s.toLowerCase();
+ var i = s.indexOf(sub);
+ if (i == -1) return false;
+ return i == 0 || options.matchContains;
+ };
+
+ function add(q, value) {
+ if (length > options.cacheLength){
+ flush();
+ }
+ if (!data[q]){
+ length++;
+ }
+ data[q] = value;
+ }
+
+ function populate(){
+ if( !options.data ) return false;
+ // track the matches
+ var stMatchSets = {},
+ nullData = 0;
+
+ // no url was specified, we need to adjust the cache length to make sure it fits the local data store
+ if( !options.url ) options.cacheLength = 1;
+
+ // track all options for minChars = 0
+ stMatchSets[""] = [];
+
+ // loop through the array and create a lookup structure
+ for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
+ var rawValue = options.data[i];
+ // if rawValue is a string, make an array otherwise just reference the array
+ rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
+
+ var value = options.formatMatch(rawValue, i+1, options.data.length);
+ if ( value === false )
+ continue;
+
+ var firstChar = value.charAt(0).toLowerCase();
+ // if no lookup array for this character exists, look it up now
+ if( !stMatchSets[firstChar] )
+ stMatchSets[firstChar] = [];
+
+ // if the match is a string
+ var row = {
+ value: value,
+ data: rawValue,
+ result: options.formatResult && options.formatResult(rawValue) || value
+ };
+
+ // push the current match into the set list
+ stMatchSets[firstChar].push(row);
+
+ // keep track of minChars zero items
+ if ( nullData++ < options.max ) {
+ stMatchSets[""].push(row);
+ }
+ };
+
+ // add the data items to the cache
+ $.each(stMatchSets, function(i, value) {
+ // increase the cache size
+ options.cacheLength++;
+ // add to the cache
+ add(i, value);
+ });
+ }
+
+ // populate any existing data
+ setTimeout(populate, 25);
+
+ function flush(){
+ data = {};
+ length = 0;
+ }
+
+ return {
+ flush: flush,
+ add: add,
+ populate: populate,
+ load: function(q) {
+ if (!options.cacheLength || !length)
+ return null;
+
+ /*
+ * if dealing w/local data and matchContains than we must make sure
+ * to loop through all the data collections looking for matches
+ */
+ if( !options.url && options.matchContains ){
+ // track all matches
+ var csub = [];
+ // loop through all the data grids for matches
+ for( var k in data ){
+ // don't search through the stMatchSets[""] (minChars: 0) cache
+ // this prevents duplicates
+ if( k.length > 0 ){
+ var c = data[k];
+ $.each(c, function(i, x) {
+ // if we've got a match, add it to the array
+ if (matchSubset(x.value, q)) {
+ csub.push(x);
+ }
+ });
+ }
+ }
+ return csub;
+ } else
+ // if the exact item exists, use it
+ if (data[q]){
+ return data[q];
+ } else
+ if (options.matchSubset) {
+ for (var i = q.length - 1; i >= options.minChars; i--) {
+ var c = data[q.substr(0, i)];
+ if (c) {
+ var csub = [];
+ $.each(c, function(i, x) {
+ if ( (x.data.indexOf("#Header") == 0) ||
+ (matchSubset(x.value, q)) ) {
+ csub[csub.length] = x;
+ }
+ });
+ return csub;
+ }
+ }
+
+ }
+ return null;
+ }
+ };
+};
+
+$.Autocompleter.Select = function (options, input, select, config) {
+ var CLASSES = {
+ ACTIVE: "ac_over"
+ };
+
+ var listItems,
+ active = -1,
+ data,
+ term = "",
+ needsInit = true,
+ element,
+ list;
+
+ // Create results
+ function init() {
+ if (!needsInit)
+ return;
+ element = $("<div/>")
+ .hide()
+ .addClass(options.resultsClass)
+ .css("position", "absolute")
+ .appendTo(document.body);
+
+ list = $("<ul/>").appendTo(element).mouseover( function(event) {
+ if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
+ active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
+ // JG: Only add active class if target is not a header.
+ if (!headerAtPosition(active))
+ $(target(event)).addClass(CLASSES.ACTIVE);
+ }
+ }).click(function(event) {
+ // JG: Ignore click on header.
+ active = $("li", list).index(target(event));
+ if (headerAtPosition(active))
+ return;
+
+ // Handle click on autocomplete options.
+ $(target(event)).addClass(CLASSES.ACTIVE);
+ select();
+ // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
+ input.focus();
+ return false;
+ }).mousedown(function() {
+ config.mouseDownOnSelect = true;
+ }).mouseup(function() {
+ config.mouseDownOnSelect = false;
+ });
+
+ if( options.width > 0 )
+ element.css("width", options.width);
+
+ needsInit = false;
+ }
+
+ function target(event) {
+ var element = event.target;
+ while(element && element.tagName != "LI")
+ element = element.parentNode;
+ // more fun with IE, sometimes event.target is empty, just ignore it then
+ if(!element)
+ return [];
+ return element;
+ }
+
+ // JG: Returns true iff there is a header element at the given position.
+ function headerAtPosition(position)
+ {
+ dataAtPosition = data[position].data;
+ return (dataAtPosition.indexOf("#Header") == 0);
+ }
+
+ function moveSelect(step) {
+ listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
+ // JG: while active item is a header, continue stepping.
+ var isHeader = false;
+ do
+ {
+ movePosition(step);
+ isHeader = headerAtPosition(active);
+ }
+ while (isHeader);
+ var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
+
+
+ if(options.scroll) {
+ var offset = 0;
+ listItems.slice(0, active).each(function() {
+ offset += this.offsetHeight;
+ });
+ if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
+ list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
+ } else if(offset < list.scrollTop()) {
+ list.scrollTop(offset);
+ }
+ }
+ };
+
+ function movePosition(step) {
+ active += step;
+ if (active < 0) {
+ active = listItems.size() - 1;
+ } else if (active >= listItems.size()) {
+ active = 0;
+ }
+ }
+
+ function limitNumberOfItems(available) {
+ return options.max && options.max < available
+ ? options.max
+ : available;
+ }
+
+ function fillList() {
+ list.empty();
+ var max = limitNumberOfItems(data.length);
+ for (var i=0; i < max; i++) {
+ if (!data[i])
+ continue;
+ var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
+ if ( formatted === false )
+ continue;
+
+ // JG: Build list item by formatting the item and choosing a CSS class.
+ if (headerAtPosition(i))
+ {
+ // Found header element; only add header if there are subsequent elements.
+ if (i != max-1)
+ var li = $("<li/>").html(data[i].data[1]).addClass("ac_header").appendTo(list)[0];
+ }
+ else
+ {
+ // Found completion element.
+ var li = $("<li/>").html(options.highlight(formatted, term)).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
+ }
+
+ $.data(li, "ac_data", data[i]);
+ }
+ listItems = list.find("li");
+ if ( options.selectFirst ) {
+ listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
+ active = 0;
+ }
+ // apply bgiframe if available
+ if ( $.fn.bgiframe )
+ list.bgiframe();
+ }
+
+ return {
+ display: function(d, q) {
+ init();
+ data = d;
+ term = q;
+ fillList();
+ },
+ next: function() {
+ moveSelect(1);
+ },
+ prev: function() {
+ moveSelect(-1);
+ },
+ pageUp: function() {
+ if (active != 0 && active - 8 < 0) {
+ moveSelect( -active );
+ } else {
+ moveSelect(-8);
+ }
+ },
+ pageDown: function() {
+ if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
+ moveSelect( listItems.size() - 1 - active );
+ } else {
+ moveSelect(8);
+ }
+ },
+ hide: function() {
+ element && element.hide();
+ listItems && listItems.removeClass(CLASSES.ACTIVE);
+ active = -1;
+ },
+ visible : function() {
+ return element && element.is(":visible");
+ },
+ current: function() {
+ return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
+ },
+ show: function() {
+ var offset = $(input).offset();
+ element.css({
+ width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
+ top: offset.top + input.offsetHeight,
+ left: offset.left
+ }).show();
+ if(options.scroll) {
+ list.scrollTop(0);
+ list.css({
+ maxHeight: options.scrollHeight,
+ overflow: 'auto'
+ });
+
+ if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
+ var listHeight = 0;
+ listItems.each(function() {
+ listHeight += this.offsetHeight;
+ });
+ var scrollbarsVisible = listHeight > options.scrollHeight;
+ list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
+ if (!scrollbarsVisible) {
+ // IE doesn't recalculate width when scrollbar disappears
+ listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
+ }
+ }
+
+ }
+ },
+ selected: function() {
+ var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
+ return selected && selected.length && $.data(selected[0], "ac_data");
+ },
+ emptyList: function (){
+ list && list.empty();
+ },
+ unbind: function() {
+ element && element.remove();
+ }
+ };
+};
+
+$.Autocompleter.Selection = function(field, start, end) {
+ if( field.createTextRange ){
+ var selRange = field.createTextRange();
+ selRange.collapse(true);
+ selRange.moveStart("character", start);
+ selRange.moveEnd("character", end);
+ selRange.select();
+ } else if( field.setSelectionRange ){
+ field.setSelectionRange(start, end);
+ } else {
+ if( field.selectionStart ){
+ field.selectionStart = start;
+ field.selectionEnd = end;
+ }
+ }
+ field.focus();
+};
+
+})(jQuery);
diff -r cc4944a62b66 -r fba947d16fa7 static/scripts/packed/autocomplete_tagging.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/scripts/packed/autocomplete_tagging.js Fri Aug 28 18:03:28 2009 -0400
@@ -0,0 +1,1 @@
+var ac_tag_area_id_gen=1;jQuery.fn.autocomplete_tagging=function(c){var e={get_toggle_link_text_fn:function(u){var w="";var v=o(u);if(v!=0){w=v+(v!=0?" Tags":" Tag")}else{w="Add tags"}return w},tag_click_fn:function(u){},input_size:20,in_form:false,tags:{},use_toggle_link:true,item_id:"",add_tag_img:"",add_tag_img_rollover:"",delete_tag_img:"",ajax_autocomplete_tag_url:"",ajax_retag_url:"",ajax_delete_tag_url:"",ajax_add_tag_url:""};var p=jQuery.extend(e,c);var k="tag-area-"+(ac_tag_area_id_gen)++;var m=$("<div></div>").attr("id",k).addClass("tag-area");this.append(m);var o=function(u){if(u.length){return u.length}var v=0;for(element in u){v++}return v};var b=function(){var u=p.get_toggle_link_text_fn(p.tags);var v=$("<a href='/history/tags'>"+u+"</a>").addClass("toggle-link");v.click(function(){var w=(m.css("display")=="none");var x;if(w){x=function(){var y=o(p.tags);if(y==0){m.click()}}}else{x=function(){m.blur()}}m.slideToggle("fast",x);return false});return v};var s=b();
if(p.use_toggle_link){this.prepend(s)}var t=function(u){var v=new Array();for(key in u){v[v.length]=key+"-->"+u[key]}return"{"+v.join(",")+"}"};var a=function(v,u){return v+((u!=""&&u)?":"+u:"")};var h=function(u){return u.split(":")};var i=function(u){var v=$("<img src='"+p.add_tag_img+"' rollover='"+p.add_tag_img_rollover+"'/>").addClass("add-tag-button");v.click(function(){$(this).hide();m.click();return false});return v};var j=function(u){var v=$("<img src='"+p.delete_tag_img+"'/>").addClass("delete-tag-img");v.mouseenter(function(){$(this).attr("src",p.delete_tag_img_rollover)});v.mouseleave(function(){$(this).attr("src",p.delete_tag_img)});v.click(function(){var B=$(this).parent();var A=B.find(".tag-name").eq(0);var z=A.text();var C=h(z)[0];B.remove();delete p.tags[C];var y=p.get_toggle_link_text_fn(p.tags);s.text(y);$.ajax({url:p.ajax_delete_tag_url,data:{tag_name:C},error:function(){alert("Remove tag failed")},success:function(){}});return true});var w=$("<span>"+u+"
</span>").addClass("tag-name");w.click(function(){p.tag_click_fn(u);return true});var x=$("<span></span>").addClass("tag-button");x.append(w);x.append(v);return x};var d=function(v){var u;if(p.in_form){u=$("<textarea id='history-tag-input' rows='1' cols='"+p.input_size+"' value='"+v+"'></textarea>")}else{u=$("<input id='history-tag-input' type='text' size='"+p.input_size+"' value='"+v+"'></input>")}u.keyup(function(D){if(D.keyCode==27){$(this).trigger("blur")}else{if((D.keyCode==13)||(D.keyCode==188)||(D.keyCode==32)){new_value=this.value;if(return_key_pressed_for_autocomplete==true){return_key_pressed_for_autocomplete=false;return false}if(new_value.indexOf(": ",new_value.length-2)!=-1){this.value=new_value.substring(0,new_value.length-1);return false}if((D.keyCode==188)||(D.keyCode==32)){new_value=new_value.substring(0,new_value.length-1)}new_value=new_value.replace(/^\s+|\s+$/g,"");if(new_value.length<3){return false}this.value="";var A=j(new_value);var z=m.children(".tag
-button");if(z.length!=0){var E=z.slice(z.length-1);E.after(A)}else{m.prepend(A)}var y=new_value.split(":");p.tags[y[0]]=y[1];var B=p.get_toggle_link_text_fn(p.tags);s.text(B);var C=$(this);$.ajax({url:p.ajax_add_tag_url,data:{new_tag:new_value},error:function(){A.remove();var F=p.get_toggle_link_text_fn(p.tags);s.text(F);alert("Add tag failed")},success:function(){C.flushCache()}});return false}}});var w=function(A,z,y,C,B){tag_name_and_value=C.split(":");return(tag_name_and_value.length==1?tag_name_and_value[0]:tag_name_and_value[1])};var x={selectFirst:false,formatItem:w,autoFill:false,highlight:false};u.autocomplete(p.ajax_autocomplete_tag_url,x);u.addClass("tag-input");return u};for(tag_name in p.tags){var q=p.tags[tag_name];var l=a(tag_name,q);var g=j(l,s,p.tags);m.append(g)}var n=d("");var f=i(n);m.blur(function(u){f.show();n.hide();m.removeClass("active-tag-area")});m.append(f);m.append(n);n.hide();m.click(function(w){var v=$(this).hasClass("active-tag-area");if($(w.
target).hasClass("delete-tag-img")&&!v){return false}if($(w.target).hasClass("tag-name")&&!v){return false}$(this).addClass("active-tag-area");f.hide();n.show();n.focus();var u=function(y){var x=m.attr("id");if(($(y.target).attr("id")!=x)&&($(y.target).parents().filter(x).length==0)){m.blur();$(document).unbind("click",u)}};$(window).click(u);return false});if(p.use_toggle_link){m.hide()}else{var r=o(p.tags);if(r==0){f.hide();n.show()}}return this.addClass("tag-element")};
\ No newline at end of file
diff -r cc4944a62b66 -r fba947d16fa7 static/scripts/packed/jquery.autocomplete.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/scripts/packed/jquery.autocomplete.js Fri Aug 28 18:03:28 2009 -0400
@@ -0,0 +1,1 @@
+String.prototype.endsWith=function(a){return(this.match(a+"$")==a)};var return_key_pressed_for_autocomplete=false;(function(a){a.fn.extend({autocomplete:function(b,c){var d=typeof b=="string";c=a.extend({},a.Autocompleter.defaults,{url:d?b:null,data:d?null:b,delay:d?a.Autocompleter.defaults.delay:10,max:c&&!c.scroll?10:150},c);c.highlight=c.highlight||function(e){return e};c.formatMatch=c.formatMatch||c.formatItem;return this.each(function(){new a.Autocompleter(this,c)})},result:function(b){return this.bind("result",b)},search:function(b){return this.trigger("search",[b])},flushCache:function(){return this.trigger("flushCache")},setOptions:function(b){return this.trigger("setOptions",[b])},unautocomplete:function(){return this.trigger("unautocomplete")}});a.Autocompleter=function(l,g){var c={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8,COLON:16};var b=a(l).attr("autocomplete","off").addClass(g.inputClass);var j;var p="";var m=a.Auto
completer.Cache(g);var e=0;var u;var x={mouseDownOnSelect:false};var r=a.Autocompleter.Select(g,l,d,x);var w;a.browser.opera&&a(l.form).bind("submit.autocomplete",function(){if(w){w=false;return false}});b.bind((a.browser.opera?"keypress":"keydown")+".autocomplete",function(y){u=y.keyCode;switch(y.keyCode){case c.UP:y.preventDefault();if(r.visible()){r.prev()}else{t(0,true)}break;case c.DOWN:y.preventDefault();if(r.visible()){r.next()}else{t(0,true)}break;case c.PAGEUP:y.preventDefault();if(r.visible()){r.pageUp()}else{t(0,true)}break;case c.PAGEDOWN:y.preventDefault();if(r.visible()){r.pageDown()}else{t(0,true)}break;case g.multiple&&a.trim(g.multipleSeparator)==","&&c.COMMA:case c.TAB:case c.RETURN:if(y.keyCode==c.RETURN){return_key_pressed_for_autocomplete=false}if(d()){y.preventDefault();w=true;if(y.keyCode==c.RETURN){return_key_pressed_for_autocomplete=true}return false}case c.ESC:r.hide();break;case c.COLON:break;default:clearTimeout(j);j=setTimeout(t,g.delay);break}})
.focus(function(){e++}).blur(function(){e=0;if(!x.mouseDownOnSelect){r.hide()}return this}).click(function(){if(e++>1&&!r.visible()){t(0,true)}return this}).bind("search",function(){var y=(arguments.length>1)?arguments[1]:null;function z(D,C){var A;if(C&&C.length){for(var B=0;B<C.length;B++){if(C[B].result.toLowerCase()==D.toLowerCase()){A=C[B];break}}}if(typeof y=="function"){y(A)}else{b.trigger("result",A&&[A.data,A.value])}}a.each(h(b.val()),function(A,B){f(B,z,z)});return this}).bind("flushCache",function(){m.flush()}).bind("setOptions",function(){a.extend(g,arguments[1]);if("data" in arguments[1]){m.populate()}}).bind("unautocomplete",function(){r.unbind();b.unbind();a(l.form).unbind(".autocomplete")});function d(){var z=r.selected();if(!z){return false}var y=z.result;p=y;if(g.multiple){var A=h(b.val());if(A.length>1){y=A.slice(0,A.length-1).join(g.multipleSeparator)+g.multipleSeparator+y}y+=g.multipleSeparator}b.val(y);v();b.trigger("result",[z.data,z.value]);return tr
ue}function t(A,z){if(u==c.DEL){r.hide();return}var y=b.val();if(!z&&y==p){return}p=y;y=i(y);if(y.length>=g.minChars){b.addClass(g.loadingClass);if(!g.matchCase){y=y.toLowerCase()}f(y,k,v)}else{n();r.hide()}}function h(z){if(!z){return[""]}var A=z.split(g.multipleSeparator);var y=[];a.each(A,function(B,C){if(a.trim(C)){y[B]=a.trim(C)}});return y}function i(y){if(!g.multiple){return y}var z=h(y);return z[z.length-1]}function q(y,z){if(g.autoFill&&(i(b.val()).toLowerCase()==y.toLowerCase())&&u!=c.BACKSPACE){b.val(b.val()+z.substring(i(p).length));a.Autocompleter.Selection(l,p.length,p.length+z.length)}}function s(){clearTimeout(j);j=setTimeout(v,200)}function v(){var y=r.visible();r.hide();clearTimeout(j);n();if(g.mustMatch){b.search(function(z){if(!z){if(g.multiple){var A=h(b.val()).slice(0,-1);b.val(A.join(g.multipleSeparator)+(A.length?g.multipleSeparator:""))}else{b.val("")}}})}if(y){a.Autocompleter.Selection(l,l.value.length,l.value.length)}}function k(z,y){if(y&&y.length
&&e){n();r.display(y,z);q(z,y[0].value);r.show()}else{v()}}function f(z,B,y){if(!g.matchCase){z=z.toLowerCase()}var A=m.load(z);if(z.endsWith(":")){A=null}if(A&&A.length){B(z,A)}else{if((typeof g.url=="string")&&(g.url.length>0)){var C={timestamp:+new Date()};a.each(g.extraParams,function(D,E){C[D]=typeof E=="function"?E():E});a.ajax({mode:"abort",port:"autocomplete"+l.name,dataType:g.dataType,url:g.url,data:a.extend({q:i(z),limit:g.max},C),success:function(E){var D=g.parse&&g.parse(E)||o(E);m.add(z,D);B(z,D)}})}else{r.emptyList();y(z)}}}function o(B){var y=[];var A=B.split("\n");for(var z=0;z<A.length;z++){var C=a.trim(A[z]);if(C){C=C.split("|");y[y.length]={data:C,value:C[0],result:g.formatResult&&g.formatResult(C,C[0])||C[0]}}}return y}function n(){b.removeClass(g.loadingClass)}};a.Autocompleter.defaults={inputClass:"ac_input",resultsClass:"ac_results",loadingClass:"ac_loading",minChars:1,delay:400,matchCase:false,matchSubset:true,matchContains:false,cacheLength:10,max:10
0,mustMatch:false,extraParams:{},selectFirst:true,formatItem:function(b){return b[0]},formatMatch:null,autoFill:false,width:0,multiple:false,multipleSeparator:", ",highlight:function(c,b){return c.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)("+b.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"<strong>$1</strong>")},scroll:true,scrollHeight:180};a.Autocompleter.Cache=function(c){var f={};var d=0;function h(l,k){if(!c.matchCase){l=l.toLowerCase()}var j=l.indexOf(k);if(j==-1){return false}return j==0||c.matchContains}function g(j,i){if(d>c.cacheLength){b()}if(!f[j]){d++}f[j]=i}function e(){if(!c.data){return false}var k={},j=0;if(!c.url){c.cacheLength=1}k[""]=[];for(var m=0,l=c.data.length;m<l;m++){var p=c.data[m];p=(typeof p=="string")?[p]:p;var o=c.formatMatch(p,m+1,c.data.length);if(o===false){continue}var n=o.charAt(0).toLowerCase();if(!k[n]){k[n]=[]}var q={value:o,data:p,result:c.formatResult&&c.formatResult(p)||o};k[n].push(q);if(j++<c.
max){k[""].push(q)}}a.each(k,function(r,s){c.cacheLength++;g(r,s)})}setTimeout(e,25);function b(){f={};d=0}return{flush:b,add:g,populate:e,load:function(n){if(!c.cacheLength||!d){return null}if(!c.url&&c.matchContains){var m=[];for(var j in f){if(j.length>0){var o=f[j];a.each(o,function(p,k){if(h(k.value,n)){m.push(k)}})}}return m}else{if(f[n]){return f[n]}else{if(c.matchSubset){for(var l=n.length-1;l>=c.minChars;l--){var o=f[n.substr(0,l)];if(o){var m=[];a.each(o,function(p,k){if((k.data.indexOf("#Header")==0)||(h(k.value,n))){m[m.length]=k}});return m}}}}}return null}}};a.Autocompleter.Select=function(e,j,l,q){var i={ACTIVE:"ac_over"};var k,f=-1,s,m="",t=true,c,p;function o(){if(!t){return}c=a("<div/>").hide().addClass(e.resultsClass).css("position","absolute").appendTo(document.body);p=a("<ul/>").appendTo(c).mouseover(function(u){if(r(u).nodeName&&r(u).nodeName.toUpperCase()=="LI"){f=a("li",p).removeClass(i.ACTIVE).index(r(u));if(!n(f)){a(r(u)).addClass(i.ACTIVE)}}}).clic
k(function(u){f=a("li",p).index(r(u));if(n(f)){return}a(r(u)).addClass(i.ACTIVE);l();j.focus();return false}).mousedown(function(){q.mouseDownOnSelect=true}).mouseup(function(){q.mouseDownOnSelect=false});if(e.width>0){c.css("width",e.width)}t=false}function r(v){var u=v.target;while(u&&u.tagName!="LI"){u=u.parentNode}if(!u){return[]}return u}function n(u){dataAtPosition=s[u].data;return(dataAtPosition.indexOf("#Header")==0)}function h(u){k.slice(f,f+1).removeClass(i.ACTIVE);var v=false;do{g(u);v=n(f)}while(v);var x=k.slice(f,f+1).addClass(i.ACTIVE);if(e.scroll){var w=0;k.slice(0,f).each(function(){w+=this.offsetHeight});if((w+x[0].offsetHeight-p.scrollTop())>p[0].clientHeight){p.scrollTop(w+x[0].offsetHeight-p.innerHeight())}else{if(w<p.scrollTop()){p.scrollTop(w)}}}}function g(u){f+=u;if(f<0){f=k.size()-1}else{if(f>=k.size()){f=0}}}function b(u){return e.max&&e.max<u?e.max:u}function d(){p.empty();var v=b(s.length);for(var w=0;w<v;w++){if(!s[w]){continue}var x=e.formatItem
(s[w].data,w+1,v,s[w].value,m);if(x===false){continue}if(n(w)){if(w!=v-1){var u=a("<li/>").html(s[w].data[1]).addClass("ac_header").appendTo(p)[0]}}else{var u=a("<li/>").html(e.highlight(x,m)).addClass(w%2==0?"ac_even":"ac_odd").appendTo(p)[0]}a.data(u,"ac_data",s[w])}k=p.find("li");if(e.selectFirst){k.slice(0,1).addClass(i.ACTIVE);f=0}if(a.fn.bgiframe){p.bgiframe()}}return{display:function(v,u){o();s=v;m=u;d()},next:function(){h(1)},prev:function(){h(-1)},pageUp:function(){if(f!=0&&f-8<0){h(-f)}else{h(-8)}},pageDown:function(){if(f!=k.size()-1&&f+8>k.size()){h(k.size()-1-f)}else{h(8)}},hide:function(){c&&c.hide();k&&k.removeClass(i.ACTIVE);f=-1},visible:function(){return c&&c.is(":visible")},current:function(){return this.visible()&&(k.filter("."+i.ACTIVE)[0]||e.selectFirst&&k[0])},show:function(){var w=a(j).offset();c.css({width:typeof e.width=="string"||e.width>0?e.width:a(j).width(),top:w.top+j.offsetHeight,left:w.left}).show();if(e.scroll){p.scrollTop(0);p.css({maxHeigh
t:e.scrollHeight,overflow:"auto"});if(a.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var u=0;k.each(function(){u+=this.offsetHeight});var v=u>e.scrollHeight;p.css("height",v?e.scrollHeight:u);if(!v){k.width(p.width()-parseInt(k.css("padding-left"))-parseInt(k.css("padding-right")))}}}},selected:function(){var u=k&&k.filter("."+i.ACTIVE).removeClass(i.ACTIVE);return u&&u.length&&a.data(u[0],"ac_data")},emptyList:function(){p&&p.empty()},unbind:function(){c&&c.remove()}}};a.Autocompleter.Selection=function(d,e,c){if(d.createTextRange){var b=d.createTextRange();b.collapse(true);b.moveStart("character",e);b.moveEnd("character",c);b.select()}else{if(d.setSelectionRange){d.setSelectionRange(e,c)}else{if(d.selectionStart){d.selectionStart=e;d.selectionEnd=c}}}d.focus()}})(jQuery);
\ No newline at end of file
diff -r cc4944a62b66 -r fba947d16fa7 templates/dataset/edit_attributes.mako
--- a/templates/dataset/edit_attributes.mako Fri Aug 28 16:52:58 2009 -0400
+++ b/templates/dataset/edit_attributes.mako Fri Aug 28 18:03:28 2009 -0400
@@ -3,6 +3,9 @@
<%def name="title()">${_('Edit Dataset Attributes')}</%def>
+<%def name="stylesheets()">
+ ${h.css( "base", "history", "autocomplete_tagging" )}
+</%def>
<%
user = trans.user
if user:
@@ -10,6 +13,47 @@
else:
user_roles = None
%>
+
+<%def name="javascripts()">
+ ## <!--[if lt IE 7]>
+ ## <script type='text/javascript' src="/static/scripts/IE7.js"> </script>
+ ## <![endif]-->
+ ${h.js( "jquery", "galaxy.base", "jquery.autocomplete", "autocomplete_tagging" )}
+ <script type="text/javascript">
+ $( document ).ready( function() {
+ // Set up autocomplete tagger.
+<%
+ ## Build string of tag name, values.
+ tag_names_and_values = list()
+ for tag in data.tags:
+ tag_name = tag.user_tname
+ tag_value = ""
+ if tag.value is not None:
+ tag_value = tag.user_value
+ tag_names_and_values.append("\"" + tag_name + "\" : \"" + tag_value + "\"")
+%>
+ var options =
+ {
+ tags : {${", ".join(tag_names_and_values)}},
+ tag_click_fn: function(tag) { /* Do nothing. */ },
+ use_toggle_link: false,
+ input_size: 30,
+ in_form: true,
+ <% encoded_data_id = trans.security.encode_id(data.id) %>
+ ajax_autocomplete_tag_url: "${h.url_for( controller='tag', action='tag_autocomplete_data', id=encoded_data_id, item_type="hda" )}",
+ ajax_add_tag_url: "${h.url_for( controller='tag', action='add_tag_async', id=encoded_data_id, item_type="hda" )}",
+ ajax_delete_tag_url: "${h.url_for( controller='tag', action='remove_tag_async', id=encoded_data_id, item_type="hda" )}",
+ delete_tag_img: "${h.url_for('/static/images/delete_tag_icon_gray.png')}",
+ delete_tag_img_rollover: "${h.url_for('/static/images/delete_tag_icon_white.png')}",
+ add_tag_img: "${h.url_for('/static/images/add_icon.png')}",
+ add_tag_img_rollover: "${h.url_for('/static/images/add_icon_dark.png')}",
+ };
+% if trans.get_user() is not None:
+ $("#dataset-tag-area").autocomplete_tagging(options);
+ });
+% endif
+ </script>
+</%def>
<%def name="datatype( dataset, datatypes )">
<select name="datatype">
@@ -45,7 +89,18 @@
<input type="text" name="info" value="${data.info}" size="40"/>
</div>
<div style="clear: both"></div>
- </div>
+ </div>
+ %if trans.get_user() is not None:
+ <div class="form-row">
+ <label>
+ Tags:
+ </label>
+ <div id="dataset-tag-area"
+ style="float: left; margin-left: 1px; width: 295px; margin-right: 10px; border-style: inset; border-color: #ddd; border-width: 1px">
+ </div>
+ <div style="clear: both"></div>
+ </div>
+ %endif
%for name, spec in data.metadata.spec.items():
%if spec.visible:
<div class="form-row">
diff -r cc4944a62b66 -r fba947d16fa7 templates/root/history.mako
--- a/templates/root/history.mako Fri Aug 28 16:52:58 2009 -0400
+++ b/templates/root/history.mako Fri Aug 28 18:03:28 2009 -0400
@@ -14,8 +14,8 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Pragma" content="no-cache">
-${h.css( "base", "history" )}
-${h.js( "jquery", "json2", "jquery.jstore-all" )}
+${h.css( "base", "history", "autocomplete_tagging" )}
+${h.js( "jquery", "json2", "jquery.jstore-all", "jquery.autocomplete", "autocomplete_tagging" )}
<script type="text/javascript">
$(function() {
@@ -83,6 +83,93 @@
%endif
%endfor
});
+
+ //
+ // Set up autocomplete tagger.
+ //
+<%
+ ## Build string of tag name, values.
+ tag_names_and_values = list()
+ for tag in history.tags:
+ tag_name = tag.user_tname
+ tag_value = ""
+ if tag.value is not None:
+ tag_value = tag.user_value
+ tag_names_and_values.append("\"" + tag_name + "\" : \"" + tag_value + "\"")
+%>
+ //
+ // Returns the number of keys (elements) in an array/dictionary.
+ //
+ var array_length = function(an_array)
+ {
+ if (an_array.length)
+ return an_array.length;
+
+ var count = 0;
+ for (element in an_array)
+ count++;
+ return count;
+ };
+
+ //
+ // Function get text to display on the toggle link.
+ //
+ var get_toggle_link_text = function(tags)
+ {
+ var text = "";
+ var num_tags = array_length(tags);
+ if (num_tags != 0)
+ {
+ text = num_tags + (num_tags != 1 ? " Tags" : " Tag");
+ /*
+ // Show first N tags; hide the rest.
+ var max_to_show = 1;
+
+ // Build tag string.
+ var tag_strs = new Array();
+ var count = 0;
+ for (tag_name in tags)
+ {
+ tag_value = tags[tag_name];
+ tag_strs[tag_strs.length] = build_tag_str(tag_name, tag_value);
+ if (++count == max_to_show)
+ break;
+ }
+ tag_str = tag_strs.join(", ");
+
+ // Finalize text.
+ var num_tags_hiding = num_tags - max_to_show;
+ text = "Tags: " + tag_str +
+ (num_tags_hiding != 0 ? " and " + num_tags_hiding + " more" : "");
+ */
+ }
+ else
+ {
+ // No tags.
+ text = "Add tags to this history";
+ }
+ return text;
+ };
+
+ var options =
+ {
+ tags : {${", ".join(tag_names_and_values)}},
+ get_toggle_link_text_fn: get_toggle_link_text,
+ input_size: 15,
+ tag_click_fn: function(tag) { /* Do nothing. */ },
+ <% encoded_history_id = trans.security.encode_id(history.id) %>
+ ajax_autocomplete_tag_url: "${h.url_for( controller='tag', action='tag_autocomplete_data', id=encoded_history_id, item_type="history" )}",
+ ajax_add_tag_url: "${h.url_for( controller='tag', action='add_tag_async', id=encoded_history_id, item_type="history" )}",
+ ajax_delete_tag_url: "${h.url_for( controller='tag', action='remove_tag_async', id=encoded_history_id, item_type="history" )}",
+ delete_tag_img: "${h.url_for('/static/images/delete_tag_icon_gray.png')}",
+ delete_tag_img_rollover: "${h.url_for('/static/images/delete_tag_icon_white.png')}",
+ add_tag_img: "${h.url_for('/static/images/add_icon.png')}",
+ add_tag_img_rollover: "${h.url_for('/static/images/add_icon_dark.png')}",
+ };
+% if trans.get_user() is not None:
+ $("#history-tag-area").autocomplete_tagging(options);
+% endif
+
});
// Functionized so AJAX'd datasets can call them
// Get shown/hidden state from cookie
@@ -290,6 +377,9 @@
<p></p>
%endif
+<div id="history-tag-area" style="margin-bottom: 1em">
+</div>
+
<%namespace file="history_common.mako" import="render_dataset" />
%if not datasets:
1
0
29 Aug '09
details: http://www.bx.psu.edu/hg/galaxy/rev/6b924dd68e77
changeset: 2653:6b924dd68e77
user: James Taylor <james(a)jamestaylor.org>
date: Fri Aug 28 18:09:43 2009 -0400
description:
Removing the galalxy.web.framework.servers module (unsued for a long time)
6 file(s) affected in this change:
lib/galaxy/web/framework/servers/__init__.py
lib/galaxy/web/framework/servers/flup/__init__.py
lib/galaxy/web/framework/servers/flup/ajp_forkthreaded.py
lib/galaxy/web/framework/servers/flup/preforkthreadedserver.py
lib/galaxy/web/framework/servers/fork_server.py
lib/galaxy/web/framework/servers/threadpool_server.py
diffs (1154 lines):
diff -r fba947d16fa7 -r 6b924dd68e77 lib/galaxy/web/framework/servers/__init__.py
--- a/lib/galaxy/web/framework/servers/__init__.py Fri Aug 28 18:03:28 2009 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-"""
-Various WSGI webserver implementations.
-"""
\ No newline at end of file
diff -r fba947d16fa7 -r 6b924dd68e77 lib/galaxy/web/framework/servers/flup/ajp_forkthreaded.py
--- a/lib/galaxy/web/framework/servers/flup/ajp_forkthreaded.py Fri Aug 28 18:03:28 2009 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,211 +0,0 @@
-# Copyright (c) 2005, 2006 Allan Saddi <allan(a)saddi.com>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-#
-# $Id: ajp_fork.py 2188 2006-12-05 22:11:45Z asaddi $
-
-"""
-ajp - an AJP 1.3/WSGI gateway.
-
-For more information about AJP and AJP connectors for your web server, see
-<http://jakarta.apache.org/tomcat/connectors-doc/>.
-
-For more information about the Web Server Gateway Interface, see
-<http://www.python.org/peps/pep-0333.html>.
-
-Example usage:
-
- #!/usr/bin/env python
- import sys
- from myapplication import app # Assume app is your WSGI application object
- from ajp import WSGIServer
- ret = WSGIServer(app).run()
- sys.exit(ret and 42 or 0)
-
-See the documentation for WSGIServer for more information.
-
-About the bit of logic at the end:
-Upon receiving SIGHUP, the python script will exit with status code 42. This
-can be used by a wrapper script to determine if the python script should be
-re-run. When a SIGINT or SIGTERM is received, the script exits with status
-code 0, possibly indicating a normal exit.
-
-Example wrapper script:
-
- #!/bin/sh
- STATUS=42
- while test $STATUS -eq 42; do
- python "$@" that_script_above.py
- STATUS=$?
- done
-
-Example workers.properties (for mod_jk):
-
- worker.list=foo
- worker.foo.port=8009
- worker.foo.host=localhost
- worker.foo.type=ajp13
-
-Example httpd.conf (for mod_jk):
-
- JkWorkersFile /path/to/workers.properties
- JkMount /* foo
-
-Note that if you mount your ajp application anywhere but the root ("/"), you
-SHOULD specifiy scriptName to the WSGIServer constructor. This will ensure
-that SCRIPT_NAME/PATH_INFO are correctly deduced.
-"""
-
-__author__ = 'Allan Saddi <allan(a)saddi.com>'
-__version__ = '$Revision: 2188 $'
-
-import socket
-import logging
-
-from flup.server.ajp_base import BaseAJPServer, Connection
-from preforkthreadedserver import PreforkThreadedServer
-
-__all__ = ['WSGIServer']
-
-class WSGIServer(BaseAJPServer, PreforkThreadedServer):
- """
- AJP1.3/WSGI server. Runs your WSGI application as a persistant program
- that understands AJP1.3. Opens up a TCP socket, binds it, and then
- waits for forwarded requests from your webserver.
-
- Why AJP? Two good reasons are that AJP provides load-balancing and
- fail-over support. Personally, I just wanted something new to
- implement. :)
-
- Of course you will need an AJP1.3 connector for your webserver (e.g.
- mod_jk) - see <http://jakarta.apache.org/tomcat/connectors-doc/>.
- """
- def __init__(self, application, scriptName='', environ=None,
- bindAddress=('localhost', 8009), allowedServers=None,
- loggingLevel=logging.INFO, debug=True, **kw):
- """
- scriptName is the initial portion of the URL path that "belongs"
- to your application. It is used to determine PATH_INFO (which doesn't
- seem to be passed in). An empty scriptName means your application
- is mounted at the root of your virtual host.
-
- environ, which must be a dictionary, can contain any additional
- environment variables you want to pass to your application.
-
- bindAddress is the address to bind to, which must be a tuple of
- length 2. The first element is a string, which is the host name
- or IPv4 address of a local interface. The 2nd element is the port
- number.
-
- allowedServers must be None or a list of strings representing the
- IPv4 addresses of servers allowed to connect. None means accept
- connections from anywhere.
-
- loggingLevel sets the logging level of the module-level logger.
- """
- BaseAJPServer.__init__(self, application,
- scriptName=scriptName,
- environ=environ,
- multithreaded=False,
- multiprocess=True,
- bindAddress=bindAddress,
- allowedServers=allowedServers,
- loggingLevel=loggingLevel,
- debug=debug)
- for key in ('multithreaded', 'multiprocess', 'jobClass', 'jobArgs'):
- if kw.has_key(key):
- del kw[key]
- PreforkThreadedServer.__init__(self, jobClass=Connection, jobArgs=(self,), **kw)
-
- def run(self):
- """
- Main loop. Call this after instantiating WSGIServer. SIGHUP, SIGINT,
- SIGQUIT, SIGTERM cause it to cleanup and return. (If a SIGHUP
- is caught, this method returns True. Returns False otherwise.)
- """
- self.logger.info('%s starting up', self.__class__.__name__)
-
- try:
- sock = self._setupSocket()
- except socket.error, e:
- self.logger.error('Failed to bind socket (%s), exiting', e[1])
- return False
-
- ret = PreforkThreadedServer.run(self, sock)
-
- self._cleanupSocket(sock)
-
- self.logger.info('%s shutting down%s', self.__class__.__name__,
- self._hupReceived and ' (reload requested)' or '')
-
- return ret
-
-def paste_factory_helper(wsgiServerClass, global_conf, host, port, **local_conf):
- # I think I can't write a tuple for bindAddress in .ini file
- host = host or global_conf.get('host', 'localhost')
- port = port or global_conf.get('port', 4000)
-
- local_conf['bindAddress'] = (host, int(port))
-
- def server(application):
- server = wsgiServerClass(application, **local_conf)
- server.run()
-
- return server
-
-def factory(global_conf, host=None, port=None, **local):
- return paste_factory_helper(WSGIServer, global_conf, host, port, **local)
-
-if __name__ == '__main__':
- def test_app(environ, start_response):
- """Probably not the most efficient example."""
- import cgi
- start_response('200 OK', [('Content-Type', 'text/html')])
- yield '<html><head><title>Hello World!</title></head>\n' \
- '<body>\n' \
- '<p>Hello World!</p>\n' \
- '<table border="1">'
- names = environ.keys()
- names.sort()
- for name in names:
- yield '<tr><td>%s</td><td>%s</td></tr>\n' % (
- name, cgi.escape(`environ[name]`))
-
- form = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ,
- keep_blank_values=1)
- if form.list:
- yield '<tr><th colspan="2">Form data</th></tr>'
-
- for field in form.list:
- yield '<tr><td>%s</td><td>%s</td></tr>\n' % (
- field.name, field.value)
-
- yield '</table>\n' \
- '</body></html>\n'
-
- from wsgiref import validate
- test_app = validate.validator(test_app)
- # Explicitly set bindAddress to *:4001 for testing.
- WSGIServer(test_app,
- bindAddress=('', 4001), allowedServers=None,
- loggingLevel=logging.DEBUG).run()
diff -r fba947d16fa7 -r 6b924dd68e77 lib/galaxy/web/framework/servers/flup/preforkthreadedserver.py
--- a/lib/galaxy/web/framework/servers/flup/preforkthreadedserver.py Fri Aug 28 18:03:28 2009 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,469 +0,0 @@
-# Copyright (c) 2005 Allan Saddi <allan(a)saddi.com>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-#
-# $Id: preforkserver.py 2311 2007-01-23 00:05:04Z asaddi $
-
-__author__ = 'Allan Saddi <allan(a)saddi.com>'
-__version__ = '$Revision: 2311 $'
-
-import sys
-import os
-import socket
-import select
-import errno
-import signal
-import threading
-
-try:
- import fcntl
-except ImportError:
- def setCloseOnExec(sock):
- pass
-else:
- def setCloseOnExec(sock):
- fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC)
-
-from flup.server.threadpool import ThreadPool
-
-# If running Python < 2.4, require eunuchs module for socket.socketpair().
-# See <http://www.inoi.fi/open/trac/eunuchs>.
-if not hasattr(socket, 'socketpair'):
- try:
- import eunuchs.socketpair
- except ImportError:
- # TODO: Other alternatives? Perhaps using os.pipe()?
- raise ImportError, 'Requires eunuchs module for Python < 2.4'
-
- def socketpair():
- s1, s2 = eunuchs.socketpair.socketpair()
- p, c = (socket.fromfd(s1, socket.AF_UNIX, socket.SOCK_STREAM),
- socket.fromfd(s2, socket.AF_UNIX, socket.SOCK_STREAM))
- os.close(s1)
- os.close(s2)
- return p, c
-
- socket.socketpair = socketpair
-
-class PreforkThreadedServer(object):
- """
- A preforked server model conceptually similar to Apache httpd(2). At
- any given time, ensures there are at least minSpare children ready to
- process new requests (up to a maximum of maxChildren children total).
- If the number of idle children is ever above maxSpare, the extra
- children are killed.
-
- If maxRequests is positive, each child will only handle that many
- requests in its lifetime before exiting.
-
- jobClass should be a class whose constructor takes at least two
- arguments: the client socket and client address. jobArgs, which
- must be a list or tuple, is any additional (static) arguments you
- wish to pass to the constructor.
-
- jobClass should have a run() method (taking no arguments) that does
- the actual work. When run() returns, the request is considered
- complete and the child process moves to idle state.
- """
- def __init__(self, minSpare=32, maxSpare=32, maxChildren=32,
- maxRequests=0, maxThreads=10, jobClass=None, jobArgs=()):
- self._minSpare = int( minSpare )
- self._maxSpare = int( maxSpare )
- self._maxChildren = max(maxSpare, int( maxChildren ) )
- self._maxRequests = int( maxRequests )
- self._maxThreads = int( maxThreads )
- self._jobClass = jobClass
- self._jobArgs = jobArgs
-
- # Internal state of children. Maps pids to dictionaries with two
- # members: 'file' and 'avail'. 'file' is the socket to that
- # individidual child and 'avail' is whether or not the child is
- # free to process requests.
- self._children = {}
-
- def run(self, sock):
- """
- The main loop. Pass a socket that is ready to accept() client
- connections. Return value will be True or False indiciating whether
- or not the loop was exited due to SIGHUP.
- """
- # Set up signal handlers.
- self._keepGoing = True
- self._hupReceived = False
- self._installSignalHandlers()
-
- # Don't want operations on main socket to block.
- sock.setblocking(0)
-
- # Set close-on-exec
- setCloseOnExec(sock)
-
- # Main loop.
- while self._keepGoing:
- # Maintain minimum number of children.
- while len(self._children) < self._maxSpare:
- if not self._spawnChild(sock): break
-
- # Wait on any socket activity from live children.
- r = [x['file'] for x in self._children.values()
- if x['file'] is not None]
-
- if len(r) == len(self._children):
- timeout = None
- else:
- # There are dead children that need to be reaped, ensure
- # that they are by timing out, if necessary.
- timeout = 2
-
- try:
- r, w, e = select.select(r, [], [], timeout)
- except select.error, e:
- if e[0] != errno.EINTR:
- raise
-
- # Scan child sockets and tend to those that need attention.
- for child in r:
- # Receive status byte.
- try:
- state = child.recv(1)
- except socket.error, e:
- if e[0] in (errno.EAGAIN, errno.EINTR):
- # Guess it really didn't need attention?
- continue
- raise
- # Try to match it with a child. (Do we need a reverse map?)
- for pid,d in self._children.items():
- if child is d['file']:
- if state:
- # Set availability status accordingly.
- self._children[pid]['avail'] = state != '\x00'
- else:
- # Didn't receive anything. Child is most likely
- # dead.
- d = self._children[pid]
- d['file'].close()
- d['file'] = None
- d['avail'] = False
-
- # Reap children.
- self._reapChildren()
-
- # See who and how many children are available.
- availList = filter(lambda x: x[1]['avail'], self._children.items())
- avail = len(availList)
-
- if avail < self._minSpare:
- # Need to spawn more children.
- while avail < self._minSpare and \
- len(self._children) < self._maxChildren:
- if not self._spawnChild(sock): break
- avail += 1
- elif avail > self._maxSpare:
- # Too many spares, kill off the extras.
- pids = [x[0] for x in availList]
- pids.sort()
- pids = pids[self._maxSpare:]
- for pid in pids:
- d = self._children[pid]
- d['file'].close()
- d['file'] = None
- d['avail'] = False
-
- # Clean up all child processes.
- self._cleanupChildren()
-
- # Restore signal handlers.
- self._restoreSignalHandlers()
-
- # Return bool based on whether or not SIGHUP was received.
- return self._hupReceived
-
- def _cleanupChildren(self):
- """
- Closes all child sockets (letting those that are available know
- that it's time to exit). Sends SIGINT to those that are currently
- processing (and hopes that it finishses ASAP).
-
- Any children remaining after 10 seconds is SIGKILLed.
- """
- # Let all children know it's time to go.
- for pid,d in self._children.items():
- if d['file'] is not None:
- d['file'].close()
- d['file'] = None
- if not d['avail']:
- # Child is unavailable. SIGINT it.
- try:
- os.kill(pid, signal.SIGINT)
- except OSError, e:
- if e[0] != errno.ESRCH:
- raise
-
- def alrmHandler(signum, frame):
- pass
-
- # Set up alarm to wake us up after 10 seconds.
- oldSIGALRM = signal.getsignal(signal.SIGALRM)
- signal.signal(signal.SIGALRM, alrmHandler)
- signal.alarm(10)
-
- # Wait for all children to die.
- while len(self._children):
- try:
- pid, status = os.wait()
- except OSError, e:
- if e[0] in (errno.ECHILD, errno.EINTR):
- break
- if self._children.has_key(pid):
- del self._children[pid]
-
- signal.signal(signal.SIGALRM, oldSIGALRM)
-
- # Forcefully kill any remaining children.
- for pid in self._children.keys():
- try:
- os.kill(pid, signal.SIGKILL)
- except OSError, e:
- if e[0] != errno.ESRCH:
- raise
-
- def _reapChildren(self):
- """Cleans up self._children whenever children die."""
- while True:
- try:
- pid, status = os.waitpid(-1, os.WNOHANG)
- except OSError, e:
- if e[0] == errno.ECHILD:
- break
- raise
- if pid <= 0:
- break
- if self._children.has_key(pid): # Sanity check.
- if self._children[pid]['file'] is not None:
- self._children[pid]['file'].close()
- del self._children[pid]
-
- def _spawnChild(self, sock):
- """
- Spawn a single child. Returns True if successful, False otherwise.
- """
- # This socket pair is used for very simple communication between
- # the parent and its children.
- parent, child = socket.socketpair()
- parent.setblocking(0)
- setCloseOnExec(parent)
- child.setblocking(0)
- setCloseOnExec(child)
- try:
- pid = os.fork()
- except OSError, e:
- if e[0] in (errno.EAGAIN, errno.ENOMEM):
- return False # Can't fork anymore.
- raise
- if not pid:
- # Child
- child.close()
- # Put child into its own process group.
- pid = os.getpid()
- os.setpgid(pid, pid)
- # Restore signal handlers.
- self._restoreSignalHandlers()
- # Close copies of child sockets.
- for f in [x['file'] for x in self._children.values()
- if x['file'] is not None]:
- f.close()
- self._children = {}
- try:
- # Enter main loop.
- self._child(sock, parent)
- except KeyboardInterrupt:
- pass
- sys.exit(0)
- else:
- # Parent
- parent.close()
- d = self._children[pid] = {}
- d['file'] = child
- d['avail'] = True
- return True
-
- def _isClientAllowed(self, addr):
- """Override to provide access control."""
- return True
-
- def _child(self, sock, parent):
- """Main loop for children."""
- requestCount = 0
-
- # For the moment we fix the number of threads per process exactly
- threadPool = ThreadPool( minSpare=self._maxThreads,
- maxSpare=self._maxThreads,
- maxThreads=self._maxThreads )
-
- activeThreads = [0]
- activeThreadsLock = threading.Lock()
-
- underCapacity = threading.Event()
- underCapacity.set()
-
- def jobFinished():
- activeThreadsLock.acquire()
- try:
- if activeThreads[0] == self._maxThreads:
- underCapacity.set()
- # Tell parent we're free again.
- try:
- parent.send('\xff')
- except socket.error, e:
- if e[0] == errno.EPIPE:
- # Parent is gone.
- return
- raise
- activeThreads[0] -= 1
- finally:
- activeThreadsLock.release()
-
- class JobClassWrapper( object ):
- def __init__( self, job ):
- self.job = job
- def run( self ):
- self.job.run()
- jobFinished()
-
- while True:
-
- # If all threads are busy, block
- underCapacity.wait()
-
- # Wait for any activity on the main socket or parent socket.
- r, w, e = select.select([sock, parent], [], [])
-
- for f in r:
- # If there's any activity on the parent socket, it
- # means the parent wants us to die or has died itself.
- # Either way, exit.
- if f is parent:
- return
-
- # Otherwise, there's activity on the main socket...
- try:
- clientSock, addr = sock.accept()
- except socket.error, e:
- if e[0] == errno.EAGAIN:
- # Or maybe not.
- continue
- raise
-
- setCloseOnExec(clientSock)
-
- # Check if this client is allowed.
- if not self._isClientAllowed(addr):
- clientSock.close()
- continue
-
- # Notify parent if we're no longer available.
- activeThreadsLock.acquire()
- try:
- activeThreads[0] += 1
- if activeThreads[0] == self._maxThreads:
- # No longer under capacity
- underCapacity.clear()
- # Tell parent
- try:
- parent.send('\x00')
- except socket.error, e:
- # If parent is gone, finish up this request.
- if e[0] != errno.EPIPE:
- raise
- finally:
- activeThreadsLock.release()
-
- ## # Do the job.
- ## self._jobClass(clientSock, addr, *self._jobArgs).run()
-
- ## print "Dispatching job"
-
- # Hand off to Connection.
- conn = JobClassWrapper( self._jobClass(clientSock, addr, *self._jobArgs) )
- # Since we track maxThreads we can allow queueing here, just queues
- # long enough for the callback above to finish.
- if not threadPool.addJob(conn, allowQueuing=True):
- # Should never happen since we track maxThreads carefully
- # outside of the pool
- raise Exception( "Something has gone terribly wrong" )
-
- # If we've serviced the maximum number of requests, exit.
- if self._maxRequests > 0:
- requestCount += 1
- if requestCount >= self._maxRequests:
- # Need to allow threads to finish up here.
- break
-
- # Signal handlers
-
- def _hupHandler(self, signum, frame):
- self._keepGoing = False
- self._hupReceived = True
-
- def _intHandler(self, signum, frame):
- self._keepGoing = False
-
- def _chldHandler(self, signum, frame):
- # Do nothing (breaks us out of select and allows us to reap children).
- pass
-
- def _installSignalHandlers(self):
- supportedSignals = [signal.SIGINT, signal.SIGTERM]
- if hasattr(signal, 'SIGHUP'):
- supportedSignals.append(signal.SIGHUP)
-
- self._oldSIGs = [(x,signal.getsignal(x)) for x in supportedSignals]
-
- for sig in supportedSignals:
- if hasattr(signal, 'SIGHUP') and sig == signal.SIGHUP:
- signal.signal(sig, self._hupHandler)
- else:
- signal.signal(sig, self._intHandler)
-
- def _restoreSignalHandlers(self):
- """Restores previous signal handlers."""
- for signum,handler in self._oldSIGs:
- signal.signal(signum, handler)
-
-if __name__ == '__main__':
- class TestJob(object):
- def __init__(self, sock, addr):
- self._sock = sock
- self._addr = addr
- def run(self):
- print "Client connection opened from %s:%d" % self._addr
- self._sock.send('Hello World!\n')
- self._sock.setblocking(1)
- self._sock.recv(1)
- self._sock.close()
- print "Client connection closed from %s:%d" % self._addr
- sock = socket.socket()
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- sock.bind(('', 8080))
- sock.listen(socket.SOMAXCONN)
- PreforkThreadedServer(maxChildren=10, jobClass=TestJob).run(sock)
diff -r fba947d16fa7 -r 6b924dd68e77 lib/galaxy/web/framework/servers/fork_server.py
--- a/lib/galaxy/web/framework/servers/fork_server.py Fri Aug 28 18:03:28 2009 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,231 +0,0 @@
-"""
-HTTPServer implementation that uses a thread pool based SocketServer (similar
-to the approach used by CherryPy) and the WSGIHandler request handler from
-Paste.
-
-NOTE: NOT HEAVILY TESTED, DO NOT USE IN PRODUCTION!
-"""
-
-import SocketServer
-import Queue
-import threading
-import thread
-import sys
-import socket
-import select
-import os
-import signal
-import errno
-
-import logging
-log = logging.getLogger( __name__ )
-
-import pkg_resources;
-pkg_resources.require( "Paste" )
-from paste.httpserver import WSGIHandler
-
-class ThreadPool( object ):
- """
- Generic thread pool with a queue of callables to consume
- """
- SHUTDOWN = object()
- def __init__( self, nworkers, name="ThreadPool" ):
- """
- Create thread pool with `nworkers` worker threads
- """
- self.nworkers = nworkers
- self.name = name
- self.queue = Queue.Queue()
- self.workers = []
- self.worker_tracker = {}
- for i in range( self.nworkers ):
- worker = threading.Thread( target=self.worker_thread_callback,
- name=( "%s worker %d" % ( self.name, i ) ) )
- worker.start()
- self.workers.append( worker )
- def worker_thread_callback( self ):
- """
- Worker thread should call this method to get and process queued
- callables
- """
- while 1:
- runnable = self.queue.get()
- if runnable is ThreadPool.SHUTDOWN:
- return
- else:
- self.worker_tracker[thread.get_ident()] = [None, None]
- try:
- runnable()
- finally:
- try:
- del self.worker_tracker[thread.get_ident()]
- except KeyError:
- pass
- def shutdown( self ):
- """
- Shutdown the queue (after finishing any pending requests)
- """
- # Add a shutdown request for every worker
- for i in range( self.nworkers ):
- self.queue.put( ThreadPool.SHUTDOWN )
- # Wait for each thread to terminate
- for worker in self.workers:
- worker.join()
-
-class PreforkThreadPoolServer( SocketServer.TCPServer ):
- """
- Server that uses a pool of threads for request handling
- """
- allow_reuse_address = 1
- def __init__( self, server_address, request_handler, nworkers, nprocesses ):
- # Create and start the workers
- self.nprocesses = nprocesses
- self.nworkers = nworkers
- self.running = True
- assert nworkers > 0, "ThreadPoolServer must have at least one worker"
- # Call the base class constructor
- SocketServer.TCPServer.__init__( self, server_address, request_handler )
-
- def get_request( self ):
- self.socket_lock.acquire()
- try:
- return self.socket.accept()
- finally:
- self.socket_lock.release()
-
- def serve_forever(self):
- """
- Overrides `serve_forever` to shutdown cleanly.
- """
- log.info( "Serving requests..." )
- # Pre-fork each child
- children = []
- for i in range( self.nprocesses ):
- pid = os.fork()
- if pid:
- # We are in the parent process
- children.append( pid )
- else:
- # We are in the child process
- signal.signal( signal.SIGINT, self.child_sigint_handler )
- self.time_to_terminate = threading.Event()
- self.socket_lock = threading.Lock()
- self.pid = os.getpid()
- self.serve_forever_child()
- sys.exit( 0 )
- # Wait
- try:
- while len( children ) > 0:
- pid, status = os.wait()
- children.remove( pid )
- except KeyboardInterrupt:
- # Cleanup, kill all children
- print "Killing Children"
- for child in children:
- os.kill( child, signal.SIGINT )
- # Setup and alarm for 10 seconds
- signal.signal( signal.SIGALRM, lambda x, y: None )
- signal.alarm( 10 )
- # Wait
- while len( children ) > 0:
- try:
- pid, status = os.wait()
- children.remove( pid )
- except OSError, e:
- if e[0] in (errno.ECHILD, errno.EINTR):
- break
- # Kill any left
- print "Killing"
- for child in children:
- os.kill( child, signal.SIGKILL )
- log.info( "Shutting down..." )
-
- def serve_forever_child( self ):
- # self.thread_pool = ThreadPool( self.nworkers, "ThreadPoolServer on %s:%d" % self.server_address )
- self.workers = []
- for i in range( self.nworkers ):
- worker = threading.Thread( target=self.serve_forever_thread )
- worker.start()
- self.workers.append( worker )
- self.time_to_terminate.wait()
- print "Terminating"
- for thread in self.workers:
- thread.join()
- self.socket.close()
-
- def serve_forever_thread( self ):
- while self.running:
- self.handle_request()
-
- def child_sigint_handler( self, signum, frame ):
- print "Shutting down child"
- self.shutdown()
-
- def shutdown( self ):
- """
- Finish pending requests and shutdown the server
- """
- self.running = False
- self.time_to_terminate.set()
-
- ## def server_activate(self):
- ## """
- ## Overrides server_activate to set timeout on our listener socket
- ## """
- ## # We set the timeout here so that we can trap ^C on windows
- ## self.socket.settimeout(1)
- ## SocketServer.TCPServer.server_activate(self)
-
-class WSGIPreforkThreadPoolServer( PreforkThreadPoolServer ):
- """
- Server that mixes ThreadPoolServer and WSGIHandler
- """
- def __init__( self, wsgi_application, server_address, *args, **kwargs ):
- PreforkThreadPoolServer.__init__( self, server_address, WSGIHandler, *args, **kwargs )
- self.wsgi_application = wsgi_application
- self.wsgi_socket_timeout = None
- def get_request(self):
- # If there is a socket_timeout, set it on the accepted
- (conn,info) = PreforkThreadPoolServer.get_request(self)
- if self.wsgi_socket_timeout:
- conn.settimeout(self.wsgi_socket_timeout)
- return (conn, info)
-
-
-
-
-
-def serve( wsgi_app, global_conf, host="127.0.0.1", port="8080",
- server_version=None, protocol_version=None, start_loop=True,
- daemon_threads=None, socket_timeout=None, nworkers=10, nprocesses=10 ):
- """
- Similar to `paste.httpserver.serve` but using the thread pool server
- """
- server_address = ( host, int( port ) )
-
- # if server_version:
- # handler.server_version = server_version
- # handler.sys_version = None
- # if protocol_version:
- # assert protocol_version in ('HTTP/0.9','HTTP/1.0','HTTP/1.1')
- # handler.protocol_version = protocol_version
-
- server = WSGIPreforkThreadPoolServer( wsgi_app, server_address, int( nworkers ), int( nprocesses ) )
- if daemon_threads:
- server.daemon_threads = daemon_threads
- if socket_timeout:
- server.wsgi_socket_timeout = int(socket_timeout)
-
- print "serving on %s:%s" % server.server_address
- if start_loop:
- try:
- server.serve_forever()
- except KeyboardInterrupt:
- # allow CTRL+C to shutdown
- pass
- return server
-
-if __name__ == '__main__':
- from paste.wsgilib import dump_environ
- serve(dump_environ, {}, server_version="Wombles/1.0",
- protocol_version="HTTP/1.1", port="8881")
diff -r fba947d16fa7 -r 6b924dd68e77 lib/galaxy/web/framework/servers/threadpool_server.py
--- a/lib/galaxy/web/framework/servers/threadpool_server.py Fri Aug 28 18:03:28 2009 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,219 +0,0 @@
-"""
-HTTPServer implementation that uses a thread pool based SocketServer (similar
-to the approach used by CherryPy) and the WSGIHandler request handler from
-Paste.
-
-NOTE: Most of the improvments from this implementation have been moved into
- the Paste HTTP server, this should be considered deprecated.
-
-Preliminary numbers from "ab -c 50 -n 500 http://localhost:8080/", all tests
-with transaction level logging. Application processes a simple cheetah
-template (using compiled NameMapper).
-
-CherryPy 2.1
-------------
-
-Percentage of the requests served within a certain time (ms)
- 50% 354
- 66% 452
- 75% 601
- 80% 674
- 90% 2868
- 95% 3000
- 98% 3173
- 99% 3361
- 100% 6145 (last request)
-
-Paste with Paste#http server (ThreadingMixIn based)
----------------------------------------------------
-
-Percentage of the requests served within a certain time (ms)
- 50% 84
- 66% 84
- 75% 84
- 80% 84
- 90% 85
- 95% 86
- 98% 92
- 99% 97
- 100% 99 (last request)
-
-This module
------------
-
-Percentage of the requests served within a certain time (ms)
- 50% 19
- 66% 23
- 75% 26
- 80% 29
- 90% 41
- 95% 50
- 98% 70
- 99% 80
- 100% 116 (last request)
-
-"""
-
-import SocketServer
-import Queue
-import threading
-import socket
-
-import logging
-log = logging.getLogger( __name__ )
-
-import pkg_resources;
-pkg_resources.require( "Paste" )
-from paste.httpserver import WSGIHandler
-
-class ThreadPool( object ):
- """
- Generic thread pool with a queue of callables to consume
- """
- SHUTDOWN = object()
- def __init__( self, nworkers, name="ThreadPool" ):
- """
- Create thread pool with `nworkers` worker threads
- """
- self.nworkers = nworkers
- self.name = name
- self.queue = Queue.Queue()
- self.workers = []
- for i in range( self.nworkers ):
- worker = threading.Thread( target=self.worker_thread_callback,
- name=( "%s worker %d" % ( self.name, i ) ) )
- worker.start()
- self.workers.append( worker )
- def worker_thread_callback( self ):
- """
- Worker thread should call this method to get and process queued
- callables
- """
- while 1:
- runnable = self.queue.get()
- if runnable is ThreadPool.SHUTDOWN:
- return
- else:
- runnable()
- def shutdown( self ):
- """
- Shutdown the queue (after finishing any pending requests)
- """
- # Add a shutdown request for every worker
- for i in range( self.nworkers ):
- self.queue.put( ThreadPool.SHUTDOWN )
- # Wait for each thread to terminate
- for worker in self.workers:
- worker.join()
-
-class ThreadPoolServer( SocketServer.TCPServer ):
- """
- Server that uses a pool of threads for request handling
- """
- allow_reuse_address = 1
- def __init__( self, server_address, request_handler, nworkers ):
- # Create and start the workers
- self.running = True
- assert nworkers > 0, "ThreadPoolServer must have at least one worker"
- self.thread_pool = ThreadPool( nworkers, "ThreadPoolServer on %s:%d" % server_address )
- # Call the base class constructor
- SocketServer.TCPServer.__init__( self, server_address, request_handler )
- def process_request( self, request, client_address ):
- """
- Queue the request to be processed by on of the thread pool threads
- """
- # This sets the socket to blocking mode (and no timeout) since it
- # may take the thread pool a little while to get back to it. (This
- # is the default but since we set a timeout on the parent socket so
- # that we can trap interrupts we need to restore this,.)
- request.setblocking( 1 )
- # Queue processing of the request
- self.thread_pool.queue.put( lambda: self.process_request_in_thread( request, client_address ) )
- def process_request_in_thread( self, request, client_address ):
- """
- The worker thread should call back here to do the rest of the
- request processing.
- """
- try:
- self.finish_request( request, client_address )
- self.close_request( request)
- except:
- self.handle_error( request, client_address )
- self.close_request( request )
- def serve_forever(self):
- """
- Overrides `serve_forever` to shutdown cleanly.
- """
- try:
- log.info( "Serving requests..." )
- while self.running:
- try:
- self.handle_request()
- except socket.timeout:
- # Timeout is expected, gives interrupts a chance to
- # propogate, just keep handling
- pass
- log.info( "Shutting down..." )
- finally:
- self.thread_pool.shutdown()
- def shutdown( self ):
- """
- Finish pending requests and shutdown the server
- """
- self.running = False
- self.socket.close()
- def server_activate(self):
- """
- Overrides server_activate to set timeout on our listener socket
- """
- # We set the timeout here so that we can trap ^C on windows
- self.socket.settimeout(1)
- SocketServer.TCPServer.server_activate(self)
-
-class WSGIThreadPoolServer( ThreadPoolServer ):
- """
- Server that mixes ThreadPoolServer and WSGIHandler
- """
- def __init__( self, wsgi_application, server_address, *args, **kwargs ):
- ThreadPoolServer.__init__( self, server_address, WSGIHandler, *args, **kwargs )
- self.wsgi_application = wsgi_application
- self.wsgi_socket_timeout = None
- def get_request(self):
- # If there is a socket_timeout, set it on the accepted
- (conn,info) = ThreadPoolServer.get_request(self)
- if self.wsgi_socket_timeout:
- conn.settimeout(self.wsgi_socket_timeout)
- return (conn, info)
-
-def serve( wsgi_app, global_conf, host="127.0.0.1", port="8080",
- server_version=None, protocol_version=None, start_loop=True,
- daemon_threads=None, socket_timeout=None, nworkers=10 ):
- """
- Similar to `paste.httpserver.serve` but using the thread pool server
- """
- server_address = ( host, int( port ) )
-
- if server_version:
- handler.server_version = server_version
- handler.sys_version = None
- if protocol_version:
- assert protocol_version in ('HTTP/0.9','HTTP/1.0','HTTP/1.1')
- handler.protocol_version = protocol_version
-
- server = WSGIThreadPoolServer( wsgi_app, server_address, int( nworkers ) )
- if daemon_threads:
- server.daemon_threads = daemon_threads
- if socket_timeout:
- server.wsgi_socket_timeout = int(socket_timeout)
-
- print "serving on %s:%s" % server.server_address
- if start_loop:
- try:
- server.serve_forever()
- except KeyboardInterrupt:
- # allow CTRL+C to shutdown
- pass
- return server
-
-
-
1
0
details: http://www.bx.psu.edu/hg/galaxy/rev/7aa2475b7fa9
changeset: 2648:7aa2475b7fa9
user: jeremy goecks <jeremy.goecks(a)emory.edu>
date: Fri Aug 28 15:40:58 2009 -0400
description:
add tagging to HDAs
1 file(s) affected in this change:
templates/dataset/edit_attributes.mako
diffs (74 lines):
diff -r 77dfb7834a94 -r 7aa2475b7fa9 templates/dataset/edit_attributes.mako
--- a/templates/dataset/edit_attributes.mako Fri Aug 28 15:40:32 2009 -0400
+++ b/templates/dataset/edit_attributes.mako Fri Aug 28 15:40:58 2009 -0400
@@ -3,6 +3,50 @@
<%def name="title()">${_('Edit Dataset Attributes')}</%def>
+<%def name="stylesheets()">
+ ${h.css( "base", "history", "autocomplete_tagging" )}
+</%def>
+
+<%def name="javascripts()">
+ ## <!--[if lt IE 7]>
+ ## <script type='text/javascript' src="/static/scripts/IE7.js"> </script>
+ ## <![endif]-->
+ ${h.js( "jquery", "galaxy.base", "jquery.autocomplete", "autocomplete_tagging" )}
+ <script type="text/javascript">
+ $( document ).ready( function() {
+ // Set up autocomplete tagger.
+<%
+ ## Build string of tag name, values.
+ tag_names_and_values = list()
+ for tag in data.tags:
+ tag_name = tag.user_tname
+ tag_value = ""
+ if tag.value is not None:
+ tag_value = tag.user_value
+ tag_names_and_values.append("\"" + tag_name + "\" : \"" + tag_value + "\"")
+%>
+ var options =
+ {
+ tags : {${", ".join(tag_names_and_values)}},
+ tag_click_fn: function(tag) { /* Do nothing. */ },
+ use_toggle_link: false,
+ input_size: 30,
+ in_form: true,
+ <% encoded_data_id = trans.security.encode_id(data.id) %>
+ ajax_autocomplete_tag_url: "${h.url_for( controller='tag', action='tag_autocomplete_data', id=encoded_data_id, item_type="hda" )}",
+ ajax_add_tag_url: "${h.url_for( controller='tag', action='add_tag_async', id=encoded_data_id, item_type="hda" )}",
+ ajax_delete_tag_url: "${h.url_for( controller='tag', action='remove_tag_async', id=encoded_data_id, item_type="hda" )}",
+ delete_tag_img: "${h.url_for('/static/images/delete_tag_icon_gray.png')}",
+ delete_tag_img_rollover: "${h.url_for('/static/images/delete_tag_icon_white.png')}",
+ add_tag_img: "${h.url_for('/static/images/add_icon.png')}",
+ add_tag_img_rollover: "${h.url_for('/static/images/add_icon_dark.png')}",
+ };
+% if trans.get_user() is not None:
+ $("#dataset-tag-area").autocomplete_tagging(options);
+ });
+% endif
+ </script>
+</%def>
<%def name="datatype( dataset, datatypes )">
<select name="datatype">
@@ -38,7 +82,18 @@
<input type="text" name="info" value="${data.info}" size="40"/>
</div>
<div style="clear: both"></div>
- </div>
+ </div>
+ %if trans.get_user() is not None:
+ <div class="form-row">
+ <label>
+ Tags:
+ </label>
+ <div id="dataset-tag-area"
+ style="float: left; margin-left: 1px; width: 295px; margin-right: 10px; border-style: inset; border-color: #ddd; border-width: 1px">
+ </div>
+ <div style="clear: both"></div>
+ </div>
+ %endif
%for name, spec in data.metadata.spec.items():
%if spec.visible:
<div class="form-row">
1
0
29 Aug '09
details: http://www.bx.psu.edu/hg/galaxy/rev/3ef81a4d574e
changeset: 2649:3ef81a4d574e
user: jeremy goecks <jeremy.goecks(a)emory.edu>
date: Fri Aug 28 15:42:37 2009 -0400
description:
tags module for handling and processing tags
2 file(s) affected in this change:
lib/galaxy/tags/__init__.py
lib/galaxy/tags/tag_handler.py
diffs (238 lines):
diff -r 7aa2475b7fa9 -r 3ef81a4d574e lib/galaxy/tags/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/tags/__init__.py Fri Aug 28 15:42:37 2009 -0400
@@ -0,0 +1,3 @@
+"""
+Galaxy tagging classes and methods.
+"""
diff -r 7aa2475b7fa9 -r 3ef81a4d574e lib/galaxy/tags/tag_handler.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/galaxy/tags/tag_handler.py Fri Aug 28 15:42:37 2009 -0400
@@ -0,0 +1,226 @@
+from galaxy.model import Tag, History, HistoryTagAssociation, Dataset, DatasetTagAssociation, HistoryDatasetAssociation, HistoryDatasetAssociationTagAssociation
+import re
+
+class TagHandler( object ):
+
+ # Tag separator.
+ tag_separators = ',;'
+
+ # Hierarchy separator.
+ hierarchy_separator = '.'
+
+ # Key-value separator.
+ key_value_separators = "=:"
+
+ def __init__(self):
+ self.tag_assoc_classes = dict()
+
+ def add_tag_assoc_class(self, entity_class, tag_assoc_class):
+ self.tag_assoc_classes[entity_class] = tag_assoc_class
+
+ def get_tag_assoc_class(self, entity_class):
+ return self.tag_assoc_classes[entity_class]
+
+ # Remove a tag from an item.
+ def remove_item_tag(self, item, tag_name):
+ # Get item tag association.
+ item_tag_assoc = self._get_item_tag_assoc(item, tag_name)
+
+ # Remove association.
+ if item_tag_assoc:
+ # Delete association.
+ item_tag_assoc.delete()
+ item.tags.remove(item_tag_assoc)
+ return True
+
+ return False
+
+ # Delete tags from an item.
+ def delete_item_tags(self, item):
+ # Delete item-tag associations.
+ for tag in item.tags:
+ tag.delete()
+
+ # Delete tags from item.
+ del item.tags[:]
+
+ # Returns true if item is has a given tag.
+ def item_has_tag(self, item, tag_name):
+ # Check for an item-tag association to see if item has a given tag.
+ item_tag_assoc = self._get_item_tag_assoc(item, tag_name)
+ if item_tag_assoc:
+ return True
+ return False
+
+
+ # Apply tags to an item.
+ def apply_item_tags(self, db_session, item, tags_str):
+ # Parse tags.
+ parsed_tags = self._parse_tags(tags_str)
+
+ # Apply each tag.
+ for name, value in parsed_tags.items():
+ # Get or create item-tag association.
+ item_tag_assoc = self._get_item_tag_assoc(item, name)
+ if not item_tag_assoc:
+ #
+ # Create item-tag association.
+ #
+
+ # Create tag; if None, skip the tag (and log error).
+ tag = self._get_or_create_tag(db_session, name)
+ if not tag:
+ # Log error?
+ continue
+
+ # Create tag association based on item class.
+ item_tag_assoc_class = self.tag_assoc_classes[item.__class__]
+ item_tag_assoc = item_tag_assoc_class()
+
+ # Add tag to association.
+ item.tags.append(item_tag_assoc)
+ item_tag_assoc.tag = tag
+
+ # Apply attributes to item-tag association. Strip whitespace from user name and tag.
+ if value:
+ trimmed_value = value.strip()
+ else:
+ trimmed_value = value
+ item_tag_assoc.user_tname = name.strip()
+ item_tag_assoc.user_value = trimmed_value
+ item_tag_assoc.value = self._scrub_tag_value(value)
+
+ # Build a string from an item's tags.
+ def get_tags_str(self, tags):
+ # Return empty string if there are no tags.
+ if not tags:
+ return ""
+
+ # Create string of tags.
+ tags_str_list = list()
+ for tag in tags:
+ tag_str = tag.user_tname
+ if tag.value is not None:
+ tag_str += ":" + tag.user_value
+ tags_str_list.append(tag_str)
+ return ", ".join(tags_str_list)
+
+ # Get a Tag object from a tag string.
+ def _get_tag(self, db_session, tag_str):
+ return db_session.query(Tag).filter(Tag.name==tag_str).first()
+
+ # Create a Tag object from a tag string.
+ def _create_tag(self, db_session, tag_str):
+ tag_hierarchy = tag_str.split(self.__class__.hierarchy_separator)
+ tag_prefix = ""
+ parent_tag = None
+ for sub_tag in tag_hierarchy:
+ # Get or create subtag.
+ tag_name = tag_prefix + self._scrub_tag_name(sub_tag)
+ tag = db_session.query(Tag).filter(Tag.name==tag_name).first()
+ if not tag:
+ tag = Tag(type="generic", name=tag_name)
+
+ # Set tag parent.
+ tag.parent = parent_tag
+
+ # Update parent and tag prefix.
+ parent_tag = tag
+ tag_prefix = tag.name + self.__class__.hierarchy_separator
+ return tag
+
+ # Get or create a Tag object from a tag string.
+ def _get_or_create_tag(self, db_session, tag_str):
+ # Scrub tag; if tag is None after being scrubbed, return None.
+ scrubbed_tag_str = self._scrub_tag_name(tag_str)
+ if not scrubbed_tag_str:
+ return None
+
+ # Get item tag.
+ tag = self._get_tag(db_session, scrubbed_tag_str)
+
+ # Create tag if necessary.
+ if tag is None:
+ tag = self._create_tag(db_session, scrubbed_tag_str)
+
+ return tag
+
+ # Return ItemTagAssociation object for an item and a tag string; returns None if there is
+ # no such tag.
+ def _get_item_tag_assoc(self, item, tag_name):
+ scrubbed_tag_name = self._scrub_tag_name(tag_name)
+ for item_tag_assoc in item.tags:
+ if item_tag_assoc.tag.name == scrubbed_tag_name:
+ return item_tag_assoc
+ return None
+
+ # Returns a list of raw (tag-name, value) pairs derived from a string; method does not scrub tags.
+ # Return value is a dictionary where tag-names are keys.
+ def _parse_tags(self, tag_str):
+ # Gracefully handle None.
+ if not tag_str:
+ return dict()
+
+ # Split tags based on separators.
+ reg_exp = re.compile('[' + self.__class__.tag_separators + ']')
+ raw_tags = reg_exp.split(tag_str)
+
+ # Extract name-value pairs.
+ name_value_pairs = dict()
+ for raw_tag in raw_tags:
+ nv_pair = self._get_name_value_pair(raw_tag)
+ name_value_pairs[nv_pair[0]] = nv_pair[1]
+ return name_value_pairs
+
+ # Scrub a tag value.
+ def _scrub_tag_value(self, value):
+ # Gracefully handle None:
+ if not value:
+ return None
+
+ # Remove whitespace from value.
+ reg_exp = re.compile('\s')
+ scrubbed_value = re.sub(reg_exp, "", value)
+
+ # Lowercase and return.
+ return scrubbed_value.lower()
+
+ # Scrub a tag name.
+ def _scrub_tag_name(self, name):
+ # Gracefully handle None:
+ if not name:
+ return None
+
+ # Remove whitespace from name.
+ reg_exp = re.compile('\s')
+ scrubbed_name = re.sub(reg_exp, "", name)
+
+ # Ignore starting ':' char.
+ if scrubbed_name.startswith(self.__class__.hierarchy_separator):
+ scrubbed_name = scrubbed_name[1:]
+
+ # If name is too short or too long, return None.
+ if len(scrubbed_name) < 3 or len(scrubbed_name) > 255:
+ return None
+
+ # Lowercase and return.
+ return scrubbed_name.lower()
+
+ # Scrub a tag name list.
+ def _scrub_tag_name_list(self, tag_name_list):
+ scrubbed_tag_list = list()
+ for tag in tag_name_list:
+ scrubbed_tag_list.append(self._scrub_tag_name(tag))
+ return scrubbed_tag_list
+
+ # Get name, value pair from a tag string.
+ def _get_name_value_pair(self, tag_str):
+ # Use regular expression to parse name, value.
+ reg_exp = re.compile("[" + self.__class__.key_value_separators + "]")
+ name_value_pair = reg_exp.split(tag_str)
+
+ # Add empty slot if tag does not have value.
+ if len(name_value_pair) < 2:
+ name_value_pair.append(None)
+
+ return name_value_pair
\ No newline at end of file
1
0
details: http://www.bx.psu.edu/hg/galaxy/rev/6f43113ab087
changeset: 2645:6f43113ab087
user: jeremy goecks <jeremy.goecks(a)emory.edu>
date: Fri Aug 28 15:36:41 2009 -0400
description:
changes to the model to support tags
1 file(s) affected in this change:
lib/galaxy/model/mapping.py
diffs (94 lines):
diff -r b6b4d07ad087 -r 6f43113ab087 lib/galaxy/model/mapping.py
--- a/lib/galaxy/model/mapping.py Fri Aug 28 15:35:27 2009 -0400
+++ b/lib/galaxy/model/mapping.py Fri Aug 28 15:36:41 2009 -0400
@@ -544,6 +544,34 @@
Column( "content", TEXT )
)
+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" ) )
+
+HistoryTagAssociation.table = Table( "history_tag_association", metadata,
+ Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ),
+ Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ),
+ Column( "user_tname", TrimmedString(255), index=True),
+ Column( "value", TrimmedString(255), index=True),
+ Column( "user_value", TrimmedString(255), index=True) )
+
+DatasetTagAssociation.table = Table( "dataset_tag_association", metadata,
+ Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ),
+ Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ),
+ Column( "user_tname", TrimmedString(255), index=True),
+ Column( "value", TrimmedString(255), index=True),
+ Column( "user_value", TrimmedString(255), index=True) )
+
+HistoryDatasetAssociationTagAssociation.table = Table( "history_dataset_association_tag_association", metadata,
+ Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ),
+ Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ),
+ Column( "user_tname", TrimmedString(255), index=True),
+ Column( "value", TrimmedString(255), index=True),
+ Column( "user_value", TrimmedString(255), index=True) )
+
# With the tables defined we can define the mappers and setup the
# relationships between the model objects.
@@ -643,7 +671,8 @@
backref=backref( "parent", primaryjoin=( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ), remote_side=[HistoryDatasetAssociation.table.c.id], uselist=False ) ),
visible_children=relation(
HistoryDatasetAssociation,
- primaryjoin=( ( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ) & ( HistoryDatasetAssociation.table.c.visible == True ) ) )
+ primaryjoin=( ( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ) & ( HistoryDatasetAssociation.table.c.visible == True ) ) ),
+ tags=relation(HistoryDatasetAssociationTagAssociation, backref='history_tag_associations')
) )
assign_mapper( context, Dataset, Dataset.table,
@@ -659,7 +688,8 @@
primaryjoin=( Dataset.table.c.id == LibraryDatasetDatasetAssociation.table.c.dataset_id ) ),
active_library_associations=relation(
LibraryDatasetDatasetAssociation,
- primaryjoin=( ( Dataset.table.c.id == LibraryDatasetDatasetAssociation.table.c.dataset_id ) & ( LibraryDatasetDatasetAssociation.table.c.deleted == False ) ) )
+ primaryjoin=( ( Dataset.table.c.id == LibraryDatasetDatasetAssociation.table.c.dataset_id ) & ( LibraryDatasetDatasetAssociation.table.c.deleted == False ) ) ),
+ tags=relation(DatasetTagAssociation, backref='datasets')
) )
assign_mapper( context, HistoryDatasetAssociationDisplayAtAuthorization, HistoryDatasetAssociationDisplayAtAuthorization.table,
@@ -678,7 +708,8 @@
assign_mapper( context, History, History.table,
properties=dict( galaxy_sessions=relation( GalaxySessionToHistoryAssociation ),
datasets=relation( HistoryDatasetAssociation, backref="history", order_by=asc(HistoryDatasetAssociation.table.c.hid) ),
- active_datasets=relation( HistoryDatasetAssociation, primaryjoin=( ( HistoryDatasetAssociation.table.c.history_id == History.table.c.id ) & ( not_( HistoryDatasetAssociation.table.c.deleted ) ) ), order_by=asc( HistoryDatasetAssociation.table.c.hid ), viewonly=True )
+ active_datasets=relation( HistoryDatasetAssociation, primaryjoin=( ( HistoryDatasetAssociation.table.c.history_id == History.table.c.id ) & ( not_( HistoryDatasetAssociation.table.c.deleted ) ) ), order_by=asc( HistoryDatasetAssociation.table.c.hid ), viewonly=True ),
+ tags=relation(HistoryTagAssociation, backref="histories")
) )
assign_mapper( context, HistoryUserShareAssociation, HistoryUserShareAssociation.table,
@@ -938,6 +969,25 @@
lazy=False )
) )
+assign_mapper( context, Tag, Tag.table,
+ properties=dict( children=relation(Tag, backref=backref( 'parent', remote_side=[Tag.table.c.id] ) )
+ ) )
+
+assign_mapper( context, HistoryTagAssociation, HistoryTagAssociation.table,
+ properties=dict( tag=relation(Tag, backref="tagged_histories") ),
+ primary_key=[HistoryTagAssociation.table.c.history_id, HistoryTagAssociation.table.c.tag_id]
+ )
+
+assign_mapper( context, DatasetTagAssociation, DatasetTagAssociation.table,
+ properties=dict( tag=relation(Tag, backref="tagged_datasets") ),
+ primary_key=[DatasetTagAssociation.table.c.dataset_id, DatasetTagAssociation.table.c.tag_id]
+ )
+
+assign_mapper( context, HistoryDatasetAssociationTagAssociation, HistoryDatasetAssociationTagAssociation.table,
+ properties=dict( tag=relation(Tag, backref="tagged_history_dataset_associations") ),
+ primary_key=[HistoryDatasetAssociationTagAssociation.table.c.history_dataset_association_id, HistoryDatasetAssociationTagAssociation.table.c.tag_id]
+ )
+
def db_next_hid( self ):
"""
Override __next_hid to generate from the database in a concurrency
1
0