mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Made history grid filterable by tags (and values) and by status.
This commit is contained in:
@@ -21,8 +21,8 @@ class TagHandler( object ):
|
||||
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):
|
||||
"""Remove a tag from an item."""
|
||||
# Get item tag association.
|
||||
item_tag_assoc = self._get_item_tag_assoc(item, tag_name)
|
||||
|
||||
@@ -35,8 +35,8 @@ class TagHandler( object ):
|
||||
|
||||
return False
|
||||
|
||||
# Delete tags from an item.
|
||||
def delete_item_tags(self, item):
|
||||
"""Delete tags from an item."""
|
||||
# Delete item-tag associations.
|
||||
for tag in item.tags:
|
||||
tag.delete()
|
||||
@@ -44,8 +44,8 @@ class TagHandler( object ):
|
||||
# Delete tags from item.
|
||||
del item.tags[:]
|
||||
|
||||
# Returns true if item is has a given tag.
|
||||
def item_has_tag(self, item, tag):
|
||||
"""Returns true if item is has a given tag."""
|
||||
# Get tag name.
|
||||
if isinstance(tag, basestring):
|
||||
tag_name = tag
|
||||
@@ -59,22 +59,25 @@ class TagHandler( object ):
|
||||
return False
|
||||
|
||||
|
||||
# Apply tags to an item.
|
||||
def apply_item_tags(self, db_session, item, tags_str):
|
||||
"""Apply tags to an item."""
|
||||
# Parse tags.
|
||||
parsed_tags = self._parse_tags(tags_str)
|
||||
parsed_tags = self.parse_tags(tags_str)
|
||||
|
||||
# Apply each tag.
|
||||
for name, value in parsed_tags.items():
|
||||
# Use lowercase name for searching/creating tag.
|
||||
lc_name = name.lower()
|
||||
|
||||
# Get or create item-tag association.
|
||||
item_tag_assoc = self._get_item_tag_assoc(item, name)
|
||||
item_tag_assoc = self._get_item_tag_assoc(item, lc_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)
|
||||
tag = self._get_or_create_tag(db_session, lc_name)
|
||||
if not tag:
|
||||
# Log error?
|
||||
continue
|
||||
@@ -88,16 +91,15 @@ class TagHandler( object ):
|
||||
item_tag_assoc.tag = tag
|
||||
|
||||
# Apply attributes to item-tag association. Strip whitespace from user name and tag.
|
||||
lc_value = None
|
||||
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)
|
||||
lc_value = value.lower()
|
||||
item_tag_assoc.user_tname = name
|
||||
item_tag_assoc.user_value = value
|
||||
item_tag_assoc.value = lc_value
|
||||
|
||||
# Build a string from an item's tags.
|
||||
def get_tags_str(self, tags):
|
||||
"""Build a string from an item's tags."""
|
||||
# Return empty string if there are no tags.
|
||||
if not tags:
|
||||
return ""
|
||||
@@ -111,16 +113,18 @@ class TagHandler( object ):
|
||||
tags_str_list.append(tag_str)
|
||||
return ", ".join(tags_str_list)
|
||||
|
||||
# Get a Tag object from a tag id.
|
||||
def get_tag_by_id(self, db_session, tag_id):
|
||||
"""Get a Tag object from a tag id."""
|
||||
return db_session.query(Tag).filter(Tag.id==tag_id).first()
|
||||
|
||||
# Get a Tag object from a tag name (string).
|
||||
def get_tag_by_name(self, db_session, tag_name):
|
||||
return db_session.query(Tag).filter(Tag.name==tag_name).first()
|
||||
"""Get a Tag object from a tag name (string)."""
|
||||
if tag_name:
|
||||
return db_session.query( Tag ).filter( Tag.name==tag_name.lower() ).first()
|
||||
return None
|
||||
|
||||
# Create a Tag object from a tag string.
|
||||
def _create_tag(self, db_session, tag_str):
|
||||
"""Create a Tag object from a tag string."""
|
||||
tag_hierarchy = tag_str.split(self.__class__.hierarchy_separator)
|
||||
tag_prefix = ""
|
||||
parent_tag = None
|
||||
@@ -139,8 +143,8 @@ class TagHandler( object ):
|
||||
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):
|
||||
"""Get or create a Tag object from a tag string."""
|
||||
# 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:
|
||||
@@ -155,18 +159,18 @@ class TagHandler( object ):
|
||||
|
||||
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):
|
||||
"""Return ItemTagAssociation object for an item and a tag string; returns None if there is
|
||||
no such tag."""
|
||||
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):
|
||||
def parse_tags(self, tag_str):
|
||||
"""Returns a list of raw (tag-name, value) pairs derived from a string; method scrubs tag names and values as well.
|
||||
Return value is a dictionary where tag-names are keys."""
|
||||
# Gracefully handle None.
|
||||
if not tag_str:
|
||||
return dict()
|
||||
@@ -179,11 +183,13 @@ class TagHandler( object ):
|
||||
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]
|
||||
scrubbed_name = self._scrub_tag_name( nv_pair[0] )
|
||||
scrubbed_value = self._scrub_tag_value( nv_pair[1] )
|
||||
name_value_pairs[scrubbed_name] = scrubbed_value
|
||||
return name_value_pairs
|
||||
|
||||
# Scrub a tag value.
|
||||
def _scrub_tag_value(self, value):
|
||||
"""Scrub a tag value."""
|
||||
# Gracefully handle None:
|
||||
if not value:
|
||||
return None
|
||||
@@ -192,11 +198,10 @@ class TagHandler( object ):
|
||||
reg_exp = re.compile('\s')
|
||||
scrubbed_value = re.sub(reg_exp, "", value)
|
||||
|
||||
# Lowercase and return.
|
||||
return scrubbed_value.lower()
|
||||
return scrubbed_value
|
||||
|
||||
# Scrub a tag name.
|
||||
def _scrub_tag_name(self, name):
|
||||
"""Scrub a tag name."""
|
||||
# Gracefully handle None:
|
||||
if not name:
|
||||
return None
|
||||
@@ -213,21 +218,20 @@ class TagHandler( object ):
|
||||
if len(scrubbed_name) < 3 or len(scrubbed_name) > 255:
|
||||
return None
|
||||
|
||||
# Lowercase and return.
|
||||
return scrubbed_name.lower()
|
||||
return scrubbed_name
|
||||
|
||||
# Scrub a tag name list.
|
||||
def _scrub_tag_name_list(self, tag_name_list):
|
||||
"""Scrub a tag name list."""
|
||||
scrubbed_tag_list = list()
|
||||
for tag in tag_name_list:
|
||||
scrubbed_tag_list.append(self._scrub_tag_name(tag))
|
||||
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):
|
||||
"""Get name, value pair from a tag string."""
|
||||
# Use regular expression to parse name, value.
|
||||
reg_exp = re.compile("[" + self.__class__.key_value_separators + "]")
|
||||
name_value_pair = reg_exp.split(tag_str)
|
||||
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:
|
||||
|
||||
@@ -2,8 +2,11 @@ from galaxy.web.base.controller import *
|
||||
from galaxy.web.framework.helpers import time_ago, iff, grids
|
||||
from galaxy import util
|
||||
from galaxy.model.mapping import desc
|
||||
from galaxy.model import History
|
||||
from galaxy.model.orm import *
|
||||
from galaxy.util.json import *
|
||||
from galaxy.tags.tag_handler import TagHandler
|
||||
from sqlalchemy.sql.expression import ClauseElement
|
||||
import webhelpers, logging, operator
|
||||
from datetime import datetime
|
||||
from cgi import escape
|
||||
@@ -39,16 +42,55 @@ class HistoryListGrid( grids.Grid ):
|
||||
return dict( operation="sharing" )
|
||||
return None
|
||||
class TagsColumn( grids.GridColumn ):
|
||||
def __init__(self, col_name):
|
||||
grids.GridColumn.__init__(self, col_name)
|
||||
def __init__(self, col_name, key, filterable):
|
||||
grids.GridColumn.__init__(self, col_name, key=key, filterable=filterable)
|
||||
# Tags cannot be sorted.
|
||||
self.sortable = False
|
||||
self.tag_elt_id_gen = 0
|
||||
|
||||
def get_value( self, trans, grid, history ):
|
||||
self.tag_elt_id_gen += 1
|
||||
elt_id="tagging-elt" + str( self.tag_elt_id_gen )
|
||||
div_elt = "<div id=%s></div>" % elt_id
|
||||
return div_elt + trans.fill_template( "/tagging_common.mako", trans=trans,
|
||||
tagged_item=history, elt_id = elt_id, in_form="true", input_size="20" )
|
||||
return div_elt + trans.fill_template( "/tagging_common.mako", trans=trans, tagged_item=history,
|
||||
elt_id = elt_id, in_form="true", input_size="20", tag_click_fn="add_tag_to_grid_filter" )
|
||||
def filter( self, db_session, query, column_filter ):
|
||||
""" Modify query to include only histories with tags in column_filter. """
|
||||
if column_filter == "All":
|
||||
pass
|
||||
elif column_filter:
|
||||
# Parse filter to extract multiple tags.
|
||||
tag_handler = TagHandler()
|
||||
raw_tags = tag_handler.parse_tags( column_filter.encode("utf-8") )
|
||||
for name, value in raw_tags.items():
|
||||
tag = tag_handler.get_tag_by_name( db_session, name )
|
||||
if tag:
|
||||
query = query.filter( History.tags.any( tag_id=tag.id ) )
|
||||
if value:
|
||||
query = query.filter( History.tags.any( value=value.lower() ) )
|
||||
else:
|
||||
# Tag doesn't exist; unclear what to do here, but the literal thing to do is add the criterion, which
|
||||
# will then yield a query that returns no results.
|
||||
query = query.filter( History.tags.any( user_tname=name ) )
|
||||
return query
|
||||
def get_accepted_filters( self ):
|
||||
""" Returns a list of accepted filters for this column. """
|
||||
accepted_filter_labels_and_vals = { "All": "All" }
|
||||
accepted_filters = []
|
||||
for label, val in accepted_filter_labels_and_vals.items():
|
||||
args = { self.key: val }
|
||||
accepted_filters.append( grids.GridColumnFilter( label, args) )
|
||||
return accepted_filters
|
||||
|
||||
|
||||
class DeletedColumn( grids.GridColumn ):
|
||||
def get_accepted_filters( self ):
|
||||
""" Returns a list of accepted filters for this column. """
|
||||
accepted_filter_labels_and_vals = { "Active" : "False", "Deleted" : "True", "All": "All" }
|
||||
accepted_filters = []
|
||||
for label, val in accepted_filter_labels_and_vals.items():
|
||||
args = { self.key: val }
|
||||
accepted_filters.append( grids.GridColumnFilter( label, args) )
|
||||
return accepted_filters
|
||||
|
||||
# Grid definition
|
||||
title = "Stored histories"
|
||||
@@ -60,12 +102,12 @@ class HistoryListGrid( grids.Grid ):
|
||||
link=( lambda item: iff( item.deleted, None, dict( operation="switch", id=item.id ) ) ),
|
||||
attach_popup=True ),
|
||||
DatasetsByStateColumn( "Datasets (by state)", ncells=4 ),
|
||||
TagsColumn( "Tags"),
|
||||
TagsColumn( "Tags", key="tags", filterable=True),
|
||||
StatusColumn( "Status", attach_popup=False ),
|
||||
grids.GridColumn( "Created", key="create_time", format=time_ago ),
|
||||
grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
|
||||
# Valid for filtering but invisible
|
||||
grids.GridColumn( "Deleted", key="deleted", visible=False )
|
||||
DeletedColumn( "Status", key="deleted", visible=False, filterable=True )
|
||||
]
|
||||
operations = [
|
||||
grids.GridOperation( "Switch", allow_multiple=False, condition=( lambda item: not item.deleted ) ),
|
||||
@@ -80,9 +122,9 @@ class HistoryListGrid( grids.Grid ):
|
||||
standard_filters = [
|
||||
grids.GridColumnFilter( "Active", args=dict( deleted=False ) ),
|
||||
grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ),
|
||||
grids.GridColumnFilter( "All", args=dict( deleted='All' ) )
|
||||
grids.GridColumnFilter( "All", args=dict( deleted='All' ) ),
|
||||
]
|
||||
default_filter = dict( deleted=False )
|
||||
default_filter = dict( deleted="False", tags="All" )
|
||||
def get_current_item( self, trans ):
|
||||
return trans.get_history()
|
||||
def apply_default_filter( self, trans, query, **kwargs ):
|
||||
|
||||
@@ -34,7 +34,7 @@ class TagsController ( BaseController ):
|
||||
|
||||
self._do_security_check(trans, item)
|
||||
|
||||
self.tag_handler.apply_item_tags( trans.sa_session, item, unicode(new_tag).encode('utf-8') )
|
||||
self.tag_handler.apply_item_tags( trans.sa_session, item, new_tag.encode('utf-8') )
|
||||
trans.sa_session.flush()
|
||||
|
||||
@web.expose
|
||||
@@ -45,7 +45,7 @@ class TagsController ( BaseController ):
|
||||
|
||||
self._do_security_check(trans, item)
|
||||
|
||||
self.tag_handler.remove_item_tag( item, unicode(tag_name).encode('utf-8') )
|
||||
self.tag_handler.remove_item_tag( item, tag_name.encode('utf-8') )
|
||||
#print tag_name
|
||||
#print unicode(tag_name)
|
||||
trans.sa_session.flush()
|
||||
@@ -60,41 +60,53 @@ class TagsController ( BaseController ):
|
||||
self._do_security_check(trans, item)
|
||||
|
||||
tag_handler.delete_item_tags(item)
|
||||
self.tag_handler.apply_item_tags( trans.sa_session, item, unicode(new_tags).encode('utf-8') )
|
||||
self.tag_handler.apply_item_tags( trans.sa_session, item, new_tags.encode('utf-8') )
|
||||
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_class=None, q=None, limit=None, timestamp=None):
|
||||
def tag_autocomplete_data( self, trans, q=None, limit=None, timestamp=None, id=None, item_class=None ):
|
||||
""" Get autocomplete data for an item's tags. """
|
||||
|
||||
|
||||
#
|
||||
# Get item, do security check, and get autocomplete data.
|
||||
#
|
||||
item = self._get_item(trans, item_class, trans.security.decode_id(id))
|
||||
item = None
|
||||
if id is not None:
|
||||
item = self._get_item(trans, item_class, trans.security.decode_id(id))
|
||||
self._do_security_check(trans, item)
|
||||
|
||||
# Get item class. TODO: we should have a mapper that goes from class_name to class object.
|
||||
if item_class == 'History':
|
||||
item_class = History
|
||||
elif item_class == 'HistoryDatasetAssociation':
|
||||
item_class = HistoryDatasetAssociation
|
||||
|
||||
self._do_security_check(trans, item)
|
||||
|
||||
q = unicode(q).encode('utf-8')
|
||||
q = q.encode('utf-8')
|
||||
if q.find(":") == -1:
|
||||
return self._get_tag_autocomplete_names(trans, item, q, limit, timestamp)
|
||||
return self._get_tag_autocomplete_names(trans, q, limit, timestamp, item, item_class)
|
||||
else:
|
||||
return self._get_tag_autocomplete_values(trans, item, q, limit, timestamp)
|
||||
return self._get_tag_autocomplete_values(trans, q, limit, timestamp, item, item_class)
|
||||
|
||||
def _get_tag_autocomplete_names(self, trans, item, q, limit, timestamp):
|
||||
def _get_tag_autocomplete_names( self, trans, q, limit, timestamp, item=None, item_class=None ):
|
||||
"""Returns autocomplete data for tag names ordered from most frequently used to
|
||||
least frequently used."""
|
||||
#
|
||||
# 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__)
|
||||
# Get item's class object and item-tag association class.
|
||||
if item is None and item_class is None:
|
||||
raise RuntimeError("Both item and item_class cannot be None")
|
||||
elif item is not None:
|
||||
item_class = item.__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, func.count('*') ]
|
||||
from_obj = item_tag_assoc_class.table.join(item.table).join(Tag)
|
||||
where_clause = and_(self._get_column_for_filtering_item_by_user_id(item.__class__)==trans.get_user().id,
|
||||
from_obj = item_tag_assoc_class.table.join(item_class.table).join(Tag)
|
||||
where_clause = and_(self._get_column_for_filtering_item_by_user_id(item_class)==trans.get_user().id,
|
||||
Tag.table.c.name.like(q + "%"))
|
||||
order_by = [ func.count("*").desc() ]
|
||||
group_by = item_tag_assoc_class.table.c.tag_id
|
||||
@@ -109,18 +121,18 @@ class TagsController ( BaseController ):
|
||||
for row in result_set:
|
||||
tag = self.tag_handler.get_tag_by_id(trans.sa_session, row[0])
|
||||
|
||||
# Exclude tags that are already applied to the history.
|
||||
if self.tag_handler.item_has_tag(item, tag):
|
||||
# Exclude tags that are already applied to the item.
|
||||
if ( item is not None ) and ( self.tag_handler.item_has_tag(item, tag) ):
|
||||
continue
|
||||
# Add tag to autocomplete data. Use the most frequent name that user
|
||||
# has employed for the tag.
|
||||
tag_names = self._get_usernames_for_tag(trans.sa_session, trans.get_user(),
|
||||
tag, item.__class__, item_tag_assoc_class)
|
||||
tag, item_class, item_tag_assoc_class)
|
||||
ac_data += tag_names[0] + "|" + tag_names[0] + "\n"
|
||||
|
||||
return ac_data
|
||||
|
||||
def _get_tag_autocomplete_values(self, trans, item, q, limit, timestamp):
|
||||
def _get_tag_autocomplete_values(self, trans, q, limit, timestamp, item=None, item_class=None):
|
||||
"""Returns autocomplete data for tag values ordered from most frequently used to
|
||||
least frequently used."""
|
||||
|
||||
@@ -132,13 +144,18 @@ class TagsController ( BaseController ):
|
||||
if tag is None:
|
||||
return ""
|
||||
|
||||
# Get item-tag association class.
|
||||
item_tag_assoc_class = self.tag_handler.get_tag_assoc_class(item.__class__)
|
||||
# Get item's class object and item-tag association class.
|
||||
if item is None and item_class is None:
|
||||
raise RuntimeError("Both item and item_class cannot be None")
|
||||
elif item is not None:
|
||||
item_class = item.__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.value, func.count('*') ]
|
||||
from_obj = item_tag_assoc_class.table.join(item.table).join(Tag)
|
||||
where_clause = and_(self._get_column_for_filtering_item_by_user_id(item.__class__)==trans.get_user().id,
|
||||
from_obj = item_tag_assoc_class.table.join(item_class.table).join(Tag)
|
||||
where_clause = and_(self._get_column_for_filtering_item_by_user_id(item_class)==trans.get_user().id,
|
||||
Tag.table.c.id==tag.id,
|
||||
item_tag_assoc_class.table.c.value.like(tag_value + "%"))
|
||||
order_by = [ func.count("*").desc(), item_tag_assoc_class.table.c.value ]
|
||||
|
||||
@@ -38,19 +38,27 @@ class Grid( object ):
|
||||
query = self.apply_default_filter( trans, query, **kwargs )
|
||||
# Maintain sort state in generated urls
|
||||
extra_url_args = {}
|
||||
# Process filtering arguments
|
||||
filter_args = {}
|
||||
if self.default_filter:
|
||||
filter_args.update( self.default_filter )
|
||||
# Process filtering arguments to (a) build a query that actuates the filter and (b) builds a
|
||||
# dictionary that denotes the current filter.
|
||||
cur_filter_dict = {}
|
||||
for column in self.columns:
|
||||
if column.key:
|
||||
# Look for filter criterion in kwargs; if not found, look in default filter.
|
||||
column_filter = None
|
||||
if "f-" + column.key in kwargs:
|
||||
column_filter = kwargs.get( "f-" + column.key )
|
||||
query = column.filter( query, column_filter, filter_args )
|
||||
# Carry filter along to newly generated urls
|
||||
extra_url_args[ "f-" + column.key ] = column_filter
|
||||
if filter_args:
|
||||
query = query.filter_by( **filter_args )
|
||||
elif ( self.default_filter ) and ( column.key in self.default_filter ):
|
||||
column_filter = self.default_filter.get( column.key )
|
||||
|
||||
# If column filter found, apply it.
|
||||
if column_filter is not None:
|
||||
# Update query.
|
||||
query = column.filter( trans.sa_session, query, column_filter )
|
||||
# Upate current filter dict.
|
||||
cur_filter_dict[ column.key ] = column_filter
|
||||
# Carry filter along to newly generated urls.
|
||||
extra_url_args[ "f-" + column.key ] = column_filter.encode("utf-8")
|
||||
|
||||
# Process sort arguments
|
||||
sort_key = sort_order = None
|
||||
if 'sort' in kwargs:
|
||||
@@ -92,6 +100,7 @@ class Grid( object ):
|
||||
return trans.fill_template( self.template,
|
||||
grid=self,
|
||||
query=query,
|
||||
cur_filter_dict=cur_filter_dict,
|
||||
sort_key=sort_key,
|
||||
encoded_sort_key=encoded_sort_key,
|
||||
sort_order=sort_order,
|
||||
@@ -125,7 +134,7 @@ class Grid( object ):
|
||||
return query
|
||||
|
||||
class GridColumn( object ):
|
||||
def __init__( self, label, key=None, method=None, format=None, link=None, attach_popup=False, visible=True, ncells=1 ):
|
||||
def __init__( self, label, key=None, method=None, format=None, link=None, attach_popup=False, visible=True, ncells=1, filterable=False ):
|
||||
self.label = label
|
||||
self.key = key
|
||||
self.method = method
|
||||
@@ -134,6 +143,7 @@ class GridColumn( object ):
|
||||
self.attach_popup = attach_popup
|
||||
self.visible = visible
|
||||
self.ncells = ncells
|
||||
self.filterable = filterable
|
||||
# Currently can only sort of columns that have a database
|
||||
# representation, not purely derived.
|
||||
if self.key:
|
||||
@@ -154,20 +164,23 @@ class GridColumn( object ):
|
||||
if self.link and self.link( item ):
|
||||
return self.link( item )
|
||||
return None
|
||||
def filter( self, query, column_filter, filter_args ):
|
||||
"""
|
||||
Must modify filter_args for carrying forward, and return query
|
||||
(possibly filtered).
|
||||
"""
|
||||
def filter( self, db_session, query, column_filter ):
|
||||
""" Modify query to reflect the column filter. """
|
||||
if column_filter == "All":
|
||||
pass
|
||||
if column_filter == "True":
|
||||
filter_args[self.key] = True
|
||||
query = query.filter_by( **{ self.key: True } )
|
||||
elif column_filter == "False":
|
||||
filter_args[self.key] = False
|
||||
query = query.filter_by( **{ self.key: False } )
|
||||
elif column_filter == "All":
|
||||
del filter_args[self.key]
|
||||
return query
|
||||
def get_accepted_filters( self ):
|
||||
""" Returns a list of accepted filters for this column. """
|
||||
accepted_filters_vals = [ "False", "True", "All" ]
|
||||
accepted_filters = []
|
||||
for val in accepted_filters_vals:
|
||||
args = { self.key: val }
|
||||
accepted_filters.append( GridColumnFilter( val, args) )
|
||||
return accepted_filters
|
||||
|
||||
class GridOperation( object ):
|
||||
def __init__( self, label, key=None, condition=None, allow_multiple=True, target=None, url_args=None ):
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
.toggle-link
|
||||
{
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
padding: 0.3em;
|
||||
margin-bottom: 1em;
|
||||
width: 100%;
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
.toggle-link
|
||||
{
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
padding: 0.3em;
|
||||
margin-bottom: 1em;
|
||||
width: 100%;
|
||||
|
||||
+132
-12
@@ -1,3 +1,5 @@
|
||||
<%! from galaxy.web.framework.helpers.grids import GridColumnFilter %>
|
||||
|
||||
<%inherit file="/base.mako"/>
|
||||
<%def name="title()">${grid.title}</%def>
|
||||
|
||||
@@ -25,6 +27,74 @@
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
// Set up autocomplete for tag filter input.
|
||||
var t = $("#input-tag-filter");
|
||||
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.
|
||||
//
|
||||
}
|
||||
});
|
||||
|
||||
// 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, mustMatch: true };
|
||||
|
||||
t.autocomplete("${h.url_for( controller='tag', action='tag_autocomplete_data', item_class='History' )}", autocomplete_options);
|
||||
|
||||
//t.addClass("tag-input");
|
||||
|
||||
return t;
|
||||
});
|
||||
## Can this be moved into base.mako?
|
||||
%if refresh_frames:
|
||||
@@ -55,6 +125,25 @@
|
||||
}
|
||||
%endif
|
||||
%endif
|
||||
|
||||
//
|
||||
// Add a tag to the current grid filter; this adds the tag to the filter and then issues a request to refresh the grid.
|
||||
//
|
||||
function add_tag_to_grid_filter(tag_name, tag_value)
|
||||
{
|
||||
// Use tag as a filter: replace TAGNAME with tag_name and issue query.
|
||||
<%
|
||||
url_args = {}
|
||||
if "tags" in cur_filter_dict and cur_filter_dict["tags"] != "All":
|
||||
url_args["f-tags"] = cur_filter_dict["tags"].encode("utf-8") + ", TAGNAME"
|
||||
else:
|
||||
url_args["f-tags"] = "TAGNAME"
|
||||
%>
|
||||
var url_base = "${url( url_args )}";
|
||||
var url = url_base.replace("TAGNAME", tag_name);
|
||||
self.location = url;
|
||||
}
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
@@ -73,19 +162,50 @@
|
||||
</style>
|
||||
</%def>
|
||||
|
||||
%if grid.standard_filters:
|
||||
<div class="grid-header">
|
||||
<h2>${grid.title}</h2>
|
||||
<span class="title">Filter:</span>
|
||||
%for i, filter in enumerate( grid.standard_filters ):
|
||||
%if i > 0:
|
||||
<span>|</span>
|
||||
%endif
|
||||
<span class="filter"><a href="${url( filter.get_url_args() )}">${filter.label}</a></span>
|
||||
%endfor
|
||||
</div>
|
||||
%endif
|
||||
<div class="grid-header">
|
||||
<h2>${grid.title}</h2>
|
||||
|
||||
## Print grid filter.
|
||||
<form name="history_actions" action="javascript:add_tag_to_grid_filter($('#input-tag-filter').attr('value'))" method="get" >
|
||||
<strong>Filter: </strong>
|
||||
%for column in grid.columns:
|
||||
%if column.filterable:
|
||||
<span> by ${column.label.lower()}:</span>
|
||||
## For now, include special case to handle tags.
|
||||
%if column.key == "tags":
|
||||
%if cur_filter_dict[column.key] != "All":
|
||||
<span class="filter" "style='font-style: italic'">
|
||||
${cur_filter_dict[column.key]}
|
||||
</span>
|
||||
<span>|</span>
|
||||
%endif
|
||||
<input id="input-tag-filter" name="f-tags" type="text" value="" size="15"/>
|
||||
<span>|</span>
|
||||
%endif
|
||||
|
||||
## Handle other columns.
|
||||
%for i, filter in enumerate( column.get_accepted_filters() ):
|
||||
%if i > 0:
|
||||
<span>|</span>
|
||||
%endif
|
||||
%if cur_filter_dict[column.key] == filter.args[column.key]:
|
||||
<span class="filter" "style='font-style: italic'">${filter.label}</span>
|
||||
%else:
|
||||
<span class="filter"><a href="${url( filter.get_url_args() )}">${filter.label}</a></span>
|
||||
%endif
|
||||
%endfor
|
||||
<span> </span>
|
||||
%endif
|
||||
%endfor
|
||||
|
||||
## Link to clear all filters.
|
||||
<%
|
||||
args = { "deleted" : "False", "tags" : "All" }
|
||||
no_filter = GridColumnFilter("Clear", args)
|
||||
%>
|
||||
<span><a href="${url( no_filter.get_url_args() )}">${no_filter.label}</a></span>
|
||||
</form>
|
||||
</div>
|
||||
<form name="history_actions" action="${url()}" method="post" >
|
||||
<table class="grid">
|
||||
<thead>
|
||||
|
||||
@@ -242,6 +242,25 @@ var updater_callback = function ( tracked_datasets ) {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Function provides text for tagging 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");
|
||||
}
|
||||
else
|
||||
{
|
||||
// No tags.
|
||||
text = "Add tags to history";
|
||||
}
|
||||
return text;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -289,7 +308,7 @@ var updater_callback = function ( tracked_datasets ) {
|
||||
|
||||
%if trans.get_user() is not None:
|
||||
<div id='history-tag-area' class="tag-element"></div>
|
||||
${render_tagging_element(history, "history-tag-area")}
|
||||
${render_tagging_element(history, "history-tag-area", get_toggle_link_text_fn='get_toggle_link_text')}
|
||||
%endif
|
||||
|
||||
%if not datasets:
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
## Render a tagging element if there is a tagged_item.
|
||||
%if tagged_item is not None and elt_id is not None:
|
||||
${render_tagging_element(tagged_item, elt_id=elt_id, in_form=in_form, input_size=input_size)}
|
||||
${render_tagging_element(tagged_item, elt_id=elt_id, in_form=in_form, input_size=input_size, tag_click_fn=tag_click_fn)}
|
||||
%endif
|
||||
|
||||
## Render the tags 'tags' as an autocomplete element.
|
||||
<%def name="render_tagging_element(tagged_item, elt_id, use_toggle_link='true', in_form='false', input_size='15')">
|
||||
<%def name="render_tagging_element(tagged_item, elt_id, use_toggle_link='true', in_form='false', input_size='15', tag_click_fn='default_tag_click_fn', get_toggle_link_text_fn='default_get_toggle_link_text_fn')">
|
||||
<script type="text/javascript">
|
||||
|
||||
//
|
||||
// Set up autocomplete tagger.
|
||||
//
|
||||
@@ -39,9 +38,9 @@
|
||||
};
|
||||
|
||||
//
|
||||
// Function get text to display on the toggle link.
|
||||
// Default function get text to display on the toggle link.
|
||||
//
|
||||
var get_toggle_link_text = function(tags)
|
||||
var default_get_toggle_link_text_fn = function(tags)
|
||||
{
|
||||
var text = "";
|
||||
var num_tags = array_length(tags);
|
||||
@@ -73,30 +72,19 @@
|
||||
else
|
||||
{
|
||||
// No tags.
|
||||
text = "Add tags to history";
|
||||
text = "Add tags";
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
//
|
||||
// Function to handle a tag click.
|
||||
//
|
||||
var tag_click_fn = function(tag_name, tag_value)
|
||||
{
|
||||
/*
|
||||
alert(tag_name);
|
||||
|
||||
// Do URL request to get histories tag.
|
||||
self.location = "http://www.yahoo.com";
|
||||
*/
|
||||
};
|
||||
// Default function to handle a tag click.
|
||||
var default_tag_click_fn = function(tag_name, tag_value) {};
|
||||
|
||||
var options =
|
||||
{
|
||||
tags : ${h.to_json_string(tag_names_and_values)},
|
||||
get_toggle_link_text_fn: get_toggle_link_text,
|
||||
tag_click_fn: tag_click_fn,
|
||||
##tag_click_fn: function(name, value) { /* Do nothing. */ },
|
||||
get_toggle_link_text_fn: ${get_toggle_link_text_fn},
|
||||
tag_click_fn: ${tag_click_fn},
|
||||
<% tagged_item_id = trans.security.encode_id(tagged_item.id) %>
|
||||
ajax_autocomplete_tag_url: "${h.url_for( controller='tag', action='tag_autocomplete_data', id=tagged_item_id, item_class=tagged_item.__class__.__name__ )}",
|
||||
ajax_add_tag_url: "${h.url_for( controller='tag', action='add_tag_async', id=tagged_item_id, item_class=tagged_item.__class__.__name__ )}",
|
||||
|
||||
Reference in New Issue
Block a user