details: http://www.bx.psu.edu/hg/galaxy/rev/e3167368345a changeset: 3638:e3167368345a user: Greg Von Kuster <greg@bx.psu.edu> date: Tue Apr 13 15:35:32 2010 -0400 description: Decouple the TagHandler from the model by allowing sub-classes to point to a specified model. Add a baseline functional tests script to provide coverage for tagging histories and history items ( more coverage is needed, of course, and can easily be added to this script ). diffstat: lib/galaxy/app.py | 4 +- lib/galaxy/tags/tag_handler.py | 269 +++++++++++++---------------- lib/galaxy/web/controllers/history.py | 2 +- lib/galaxy/web/controllers/tag.py | 202 ++++++++++------------ lib/galaxy/web/controllers/user.py | 2 +- lib/galaxy/web/framework/helpers/grids.py | 6 +- templates/tagging_common.mako | 5 +- templates/user/index.mako | 3 +- test/base/twilltestcase.py | 16 + test/functional/test_tags.py | 63 +++++++ 10 files changed, 299 insertions(+), 273 deletions(-) diffs (930 lines): diff -r 4fb48981bdb0 -r e3167368345a lib/galaxy/app.py --- a/lib/galaxy/app.py Tue Apr 13 13:14:59 2010 -0400 +++ b/lib/galaxy/app.py Tue Apr 13 15:35:32 2010 -0400 @@ -1,11 +1,11 @@ import sys, os, atexit from galaxy import config, jobs, util, tools, web, cloud -## from galaxy.tracks import store from galaxy.web import security import galaxy.model import galaxy.datatypes.registry import galaxy.security +from galaxy.tags.tag_handler import GalaxyTagHandler class UniverseApplication( object ): """Encapsulates the state of a Universe application""" @@ -33,6 +33,8 @@ self.config.database_engine_options ) # Security helper self.security = security.SecurityHelper( id_secret=self.config.id_secret ) + # Tag handler + self.tag_handler = GalaxyTagHandler() # Initialize the tools self.toolbox = tools.ToolBox( self.config.tool_config, self.config.tool_path, self ) # Load datatype converters diff -r 4fb48981bdb0 -r e3167368345a lib/galaxy/tags/tag_handler.py --- a/lib/galaxy/tags/tag_handler.py Tue Apr 13 13:14:59 2010 -0400 +++ b/lib/galaxy/tags/tag_handler.py Tue Apr 13 15:35:32 2010 -0400 @@ -1,148 +1,117 @@ -from galaxy import model -import re +import re, logging from sqlalchemy.sql.expression import func, and_ from sqlalchemy.sql import select +log = logging.getLogger( __name__ ) + +# Item-specific information needed to perform tagging. +class ItemTagAssocInfo( object ): + def __init__( self, item_class, tag_assoc_class, item_id_col ): + self.item_class = item_class + self.tag_assoc_class = tag_assoc_class + self.item_id_col = item_id_col + class TagHandler( object ): - - # Minimum tag length. - min_tag_len = 2 - - # Maximum tag length. - max_tag_len = 255 - - # Tag separator. - tag_separators = ',;' - - # Hierarchy separator. - hierarchy_separator = '.' - - # Key-value separator. - key_value_separators = "=:" - - # Item-specific information needed to perform tagging. - class ItemTagAssocInfo( object ): - def __init__( self, item_class, tag_assoc_class, item_id_col ): - self.item_class = item_class - self.tag_assoc_class = tag_assoc_class - self.item_id_col = item_id_col - - # Initialize with known classes. - item_tag_assoc_info = {} - item_tag_assoc_info["History"] = ItemTagAssocInfo( model.History, model.HistoryTagAssociation, model.HistoryTagAssociation.table.c.history_id ) - item_tag_assoc_info["HistoryDatasetAssociation"] = \ - ItemTagAssocInfo( model.HistoryDatasetAssociation, model.HistoryDatasetAssociationTagAssociation, model.HistoryDatasetAssociationTagAssociation.table.c.history_dataset_association_id ) - item_tag_assoc_info["Page"] = ItemTagAssocInfo( model.Page, model.PageTagAssociation, model.PageTagAssociation.table.c.page_id ) - item_tag_assoc_info["StoredWorkflow"] = ItemTagAssocInfo( model.StoredWorkflow, model.StoredWorkflowTagAssociation, model.StoredWorkflowTagAssociation.table.c.stored_workflow_id ) - item_tag_assoc_info["Visualization"] = ItemTagAssocInfo( model.Visualization, model.VisualizationTagAssociation, model.VisualizationTagAssociation.table.c.visualization_id ) - - def get_tag_assoc_class(self, item_class): - """ Returns tag association class for item class. """ + def __init__( self ): + # Minimum tag length. + self.min_tag_len = 2 + # Maximum tag length. + self.max_tag_len = 255 + # Tag separator. + self.tag_separators = ',;' + # Hierarchy separator. + self.hierarchy_separator = '.' + # Key-value separator. + self.key_value_separators = "=:" + # Initialize with known classes - add to this in subclasses. + self.item_tag_assoc_info = {} + def get_tag_assoc_class( self, item_class ): + """Returns tag association class for item class.""" return self.item_tag_assoc_info[item_class.__name__].tag_assoc_class - - def get_id_col_in_item_tag_assoc_table( self, item_class): - """ Returns item id column in class' item-tag association table. """ + def get_id_col_in_item_tag_assoc_table( self, item_class ): + """Returns item id column in class' item-tag association table.""" return self.item_tag_assoc_info[item_class.__name__].item_id_col - - def get_community_tags(self, sa_session, item=None, limit=None): - """ Returns community tags for an item. """ - + def get_community_tags( self, trans, item=None, limit=None ): + """Returns community tags for an item.""" # Get item-tag association class. item_class = item.__class__ item_tag_assoc_class = self.get_tag_assoc_class( item_class ) if not item_tag_assoc_class: return [] - # 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_class.table ).join( model.Tag.table ) - where_clause = ( self.get_id_col_in_item_tag_assoc_table(item_class) == item.id ) - order_by = [ func.count("*").desc() ] + cols_to_select = [ item_tag_assoc_class.table.c.tag_id, func.count( '*' ) ] + from_obj = item_tag_assoc_class.table.join( item_class.table ).join( trans.app.model.Tag.table ) + where_clause = ( self.get_id_col_in_item_tag_assoc_table( item_class ) == item.id ) + order_by = [ func.count( "*" ).desc() ] group_by = item_tag_assoc_class.table.c.tag_id - # 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, limit=limit) - result_set = sa_session.execute(query) - + query = select( columns=cols_to_select, + from_obj=from_obj, + whereclause=where_clause, + group_by=group_by, + order_by=order_by, + limit=limit ) + result_set = trans.sa_session.execute( query ) # Return community tags. community_tags = [] for row in result_set: tag_id = row[0] - community_tags.append( self.get_tag_by_id( sa_session, tag_id ) ) - + community_tags.append( self.get_tag_by_id( trans, tag_id ) ) return community_tags - def remove_item_tag( self, trans, user, item, tag_name ): """Remove a tag from an item.""" # Get item tag association. - item_tag_assoc = self._get_item_tag_assoc(user, item, tag_name) - + item_tag_assoc = self._get_item_tag_assoc( user, item, tag_name ) # Remove association. if item_tag_assoc: # Delete association. trans.sa_session.delete( item_tag_assoc ) - item.tags.remove(item_tag_assoc) + item.tags.remove( item_tag_assoc ) return True - return False - def delete_item_tags( self, trans, user, item ): """Delete tags from an item.""" # Delete item-tag associations. for tag in item.tags: trans.sa_session.delete( tag ) - # Delete tags from item. del item.tags[:] - - def item_has_tag(self, user, item, tag): + def item_has_tag( self, trans, user, item, tag ): """Returns true if item is has a given tag.""" # Get tag name. - if isinstance(tag, basestring): + if isinstance( tag, basestring ): tag_name = tag - elif isinstance(tag, model.Tag): + elif isinstance( tag, trans.app.model.Tag ): tag_name = tag.name - # Check for an item-tag association to see if item has a given tag. - item_tag_assoc = self._get_item_tag_assoc(user, item, tag_name) + item_tag_assoc = self._get_item_tag_assoc( user, item, tag_name ) if item_tag_assoc: return True return False - - - def apply_item_tags(self, db_session, user, item, tags_str): + def apply_item_tags( self, trans, user, 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(user, item, lc_name) + item_tag_assoc = self._get_item_tag_assoc( user, 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, lc_name) + tag = self._get_or_create_tag( trans, lc_name ) if not tag: # Log error? continue - # Create tag association based on item class. item_tag_assoc_class = self.get_tag_assoc_class( item.__class__ ) item_tag_assoc = item_tag_assoc_class() - # Add tag to association. - item.tags.append(item_tag_assoc) + item.tags.append( item_tag_assoc ) item_tag_assoc.tag = tag - item_tag_assoc.user = user - + item_tag_assoc.user = user # Apply attributes to item-tag association. Strip whitespace from user name and tag. lc_value = None if value: @@ -150,144 +119,142 @@ item_tag_assoc.user_tname = name item_tag_assoc.user_value = value item_tag_assoc.value = lc_value - - def get_tags_str(self, 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 "" - # 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) - - def get_tag_by_id(self, db_session, tag_id): + tags_str_list.append( tag_str ) + return ", ".join( tags_str_list ) + def get_tag_by_id( self, trans, tag_id ): """Get a Tag object from a tag id.""" - return db_session.query( model.Tag ).filter_by( id=tag_id) .first() - - def get_tag_by_name(self, db_session, tag_name): + return trans.sa_session.query( trans.app.model.Tag ).filter_by( id=tag_id ).first() + def get_tag_by_name( self, trans, tag_name ): """Get a Tag object from a tag name (string).""" if tag_name: - return db_session.query( model.Tag ).filter_by( name=tag_name.lower() ).first() + return trans.sa_session.query( trans.app.model.Tag ).filter_by( name=tag_name.lower() ).first() return None - - def _create_tag(self, db_session, tag_str): + def _create_tag( self, trans, tag_str ): """Create a Tag object from a tag string.""" - tag_hierarchy = tag_str.split(self.__class__.hierarchy_separator) + tag_hierarchy = tag_str.split( self.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( model.Tag ).filter_by( name=tag_name).first() + tag_name = tag_prefix + self._scrub_tag_name( sub_tag ) + tag = trans.sa_session.query( trans.app.model.Tag ).filter_by( name=tag_name).first() if not tag: - tag = model.Tag(type=0, name=tag_name) - + tag = trans.app.model.Tag( type=0, 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 + tag_prefix = tag.name + self.hierarchy_separator return tag - - def _get_or_create_tag(self, db_session, tag_str): + def _get_or_create_tag( self, trans, 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) + scrubbed_tag_str = self._scrub_tag_name( tag_str ) if not scrubbed_tag_str: return None - # Get item tag. - tag = self.get_tag_by_name(db_session, scrubbed_tag_str) - + tag = self.get_tag_by_name( trans, scrubbed_tag_str ) # Create tag if necessary. if tag is None: - tag = self._create_tag(db_session, scrubbed_tag_str) - + tag = self._create_tag( trans, scrubbed_tag_str ) return tag - def _get_item_tag_assoc( self, user, item, tag_name ): - """Return ItemTagAssociation object for a user, item, and tag string; returns None if there is - no such association.""" + """ + Return ItemTagAssociation object for a user, item, and tag string; returns None if there is + no such association. + """ scrubbed_tag_name = self._scrub_tag_name( tag_name ) for item_tag_assoc in item.tags: if ( item_tag_assoc.user == user ) and ( item_tag_assoc.user_tname == scrubbed_tag_name ): return item_tag_assoc return None - - 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.""" + 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() - # Split tags based on separators. - reg_exp = re.compile('[' + self.__class__.tag_separators + ']') - raw_tags = reg_exp.split(tag_str) - + reg_exp = re.compile( '[' + self.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) + nv_pair = self._get_name_value_pair( raw_tag ) 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 - - def _scrub_tag_value(self, value): + def _scrub_tag_value( self, value ): """Scrub a tag 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) - + reg_exp = re.compile( '\s' ) + scrubbed_value = re.sub( reg_exp, "", value ) return scrubbed_value - - def _scrub_tag_name(self, name): + def _scrub_tag_name( self, name ): """Scrub a tag 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) - + reg_exp = re.compile( '\s' ) + scrubbed_name = re.sub( reg_exp, "", name ) # Ignore starting ':' char. - if scrubbed_name.startswith(self.__class__.hierarchy_separator): + if scrubbed_name.startswith( self.hierarchy_separator ): scrubbed_name = scrubbed_name[1:] - # If name is too short or too long, return None. - if len(scrubbed_name) < self.min_tag_len or len(scrubbed_name) > self.max_tag_len: + if len( scrubbed_name ) < self.min_tag_len or len( scrubbed_name ) > self.max_tag_len: return None - return scrubbed_name - - def _scrub_tag_name_list(self, 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 - - def _get_name_value_pair(self, tag_str): + 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 + "]" ) + reg_exp = re.compile( "[" + self.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 + if len( name_value_pair ) < 2: + name_value_pair.append( None ) + return name_value_pair + +class GalaxyTagHandler( TagHandler ): + def __init__( self ): + from galaxy import model + TagHandler.__init__( self ) + self.item_tag_assoc_info["History"] = ItemTagAssocInfo( model.History, + model.HistoryTagAssociation, + model.HistoryTagAssociation.table.c.history_id ) + self.item_tag_assoc_info["HistoryDatasetAssociation"] = \ + ItemTagAssocInfo( model.HistoryDatasetAssociation, + model.HistoryDatasetAssociationTagAssociation, + model.HistoryDatasetAssociationTagAssociation.table.c.history_dataset_association_id ) + self.item_tag_assoc_info["Page"] = ItemTagAssocInfo( model.Page, + model.PageTagAssociation, + model.PageTagAssociation.table.c.page_id ) + self.item_tag_assoc_info["StoredWorkflow"] = ItemTagAssocInfo( model.StoredWorkflow, + model.StoredWorkflowTagAssociation, + model.StoredWorkflowTagAssociation.table.c.stored_workflow_id ) + self.item_tag_assoc_info["Visualization"] = ItemTagAssocInfo( model.Visualization, + model.VisualizationTagAssociation, + model.VisualizationTagAssociation.table.c.visualization_id ) diff -r 4fb48981bdb0 -r e3167368345a lib/galaxy/web/controllers/history.py --- a/lib/galaxy/web/controllers/history.py Tue Apr 13 13:14:59 2010 -0400 +++ b/lib/galaxy/web/controllers/history.py Tue Apr 13 15:35:32 2010 -0400 @@ -5,7 +5,7 @@ from galaxy.model.orm import * from galaxy.util.json import * from galaxy.util.sanitize_html import sanitize_html -from galaxy.tags.tag_handler import TagHandler +from galaxy.tags.tag_handler import GalaxyTagHandler from sqlalchemy.sql.expression import ClauseElement import webhelpers, logging, operator from datetime import datetime diff -r 4fb48981bdb0 -r e3167368345a lib/galaxy/web/controllers/tag.py --- a/lib/galaxy/web/controllers/tag.py Tue Apr 13 13:14:59 2010 -0400 +++ b/lib/galaxy/web/controllers/tag.py Tue Apr 13 15:35:32 2010 -0400 @@ -1,203 +1,185 @@ """ Tags Controller: handles tagging/untagging of entities and provides autocomplete support. """ - +import logging from galaxy.web.base.controller import * -from galaxy.tags.tag_handler import * from sqlalchemy.sql.expression import func, and_ from sqlalchemy.sql import select +log = logging.getLogger( __name__ ) + class TagsController ( BaseController ): - - def __init__(self, app): - BaseController.__init__(self, app) - self.tag_handler = TagHandler() - + def __init__( self, app ): + BaseController.__init__( self, app ) + self.tag_handler = app.tag_handler @web.expose @web.require_login( "edit item tags" ) def get_tagging_elt_async( self, trans, item_id, item_class, elt_context="" ): """ Returns HTML for editing an item's tags. """ item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) if not item: - return trans.show_error_message( "No item of class %s with id % " % ( item_class, item_id ) ) - user = trans.get_user() - return trans.fill_template( "/tagging_common.mako", tag_type="individual", user=trans.get_user(), tagged_item=item, elt_context=elt_context, - in_form=False, input_size="22", tag_click_fn="default_tag_click_fn", use_toggle_link=False ) - + return trans.show_error_message( "No item of class %s with id %s " % ( item_class, item_id ) ) + return trans.fill_template( "/tagging_common.mako", + tag_type="individual", + user=trans.user, + tagged_item=item, + elt_context=elt_context, + in_form=False, + input_size="22", + tag_click_fn="default_tag_click_fn", + use_toggle_link=False ) @web.expose @web.require_login( "add tag to an item" ) def add_tag_async( self, trans, item_id=None, item_class=None, new_tag=None, context=None ): - """ Add tag to an item. """ - + """ Add tag to an item. """ # Apply tag. item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) - user = trans.get_user() - self.tag_handler.apply_item_tags( trans.sa_session, user, item, new_tag.encode('utf-8') ) + user = trans.user + self.tag_handler.apply_item_tags( trans, user, item, new_tag.encode( 'utf-8' ) ) trans.sa_session.flush() - # Log. - params = dict( item_id=item.id, item_class=item_class, tag=new_tag) + params = dict( item_id=item.id, item_class=item_class, tag=new_tag ) trans.log_action( user, unicode( "tag" ), context, params ) - @web.expose @web.require_login( "remove tag from an item" ) def remove_tag_async( self, trans, item_id=None, item_class=None, tag_name=None, context=None ): """ Remove tag from an item. """ - # Remove tag. - item = self._get_item( trans, item_class, trans.security.decode_id( item_id) ) - user = trans.get_user() - self.tag_handler.remove_item_tag( trans, user, item, tag_name.encode('utf-8') ) + item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) + user = trans.user + self.tag_handler.remove_item_tag( trans, user, item, tag_name.encode( 'utf-8' ) ) trans.sa_session.flush() - # Log. - params = dict( item_id=item.id, item_class=item_class, tag=tag_name) - trans.log_action( user, unicode( "untag"), context, params ) - + params = dict( item_id=item.id, item_class=item_class, tag=tag_name ) + trans.log_action( user, unicode( "untag" ), context, params ) # Retag an item. All previous tags are deleted and new tags are applied. #@web.expose @web.require_login( "Apply a new set of tags to an item; previous tags are deleted." ) def retag_async( self, trans, item_id=None, item_class=None, new_tags=None ): """ Apply a new set of tags to an item; previous tags are deleted. """ - # Apply tags. item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) - user = trans.get_user() - tag_handler.delete_item_tags( trans, item ) - self.tag_handler.apply_item_tags( trans.sa_session, user, item, new_tags.encode('utf-8') ) - trans.sa_session.flush() - + user = trans.user + self.tag_handler.delete_item_tags( trans, item ) + self.tag_handler.apply_item_tags( trans, user, 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, q=None, limit=None, timestamp=None, item_id=None, item_class=None ): """ Get autocomplete data for an item's tags. """ - - # # Get item, do security check, and get autocomplete data. - # item = None if item_id is not None: item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) - user = trans.get_user() + user = trans.user item_class = self.get_class( item_class ) - - q = q.encode('utf-8') - if q.find(":") == -1: - return self._get_tag_autocomplete_names(trans, q, limit, timestamp, user, item, item_class) + q = q.encode( 'utf-8' ) + if q.find( ":" ) == -1: + return self._get_tag_autocomplete_names( trans, q, limit, timestamp, user, item, item_class ) else: - return self._get_tag_autocomplete_values(trans, q, limit, timestamp, user, item, item_class) - + return self._get_tag_autocomplete_values( trans, q, limit, timestamp, user, item, item_class ) def _get_tag_autocomplete_names( self, trans, q, limit, timestamp, user=None, item=None, item_class=None ): - """Returns autocomplete data for tag names ordered from most frequently used to - least frequently used.""" - # + """ + 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'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") + 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) - + 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_class.table ).join( model.Tag.table ) - where_clause = and_( - model.Tag.table.c.name.like(q + "%"), - item_tag_assoc_class.table.c.user_id == user.id - ) - order_by = [ func.count("*").desc() ] + cols_to_select = [ item_tag_assoc_class.table.c.tag_id, func.count( '*' ) ] + from_obj = item_tag_assoc_class.table.join( item_class.table ).join( trans.app.model.Tag.table ) + where_clause = and_( trans.app.model.Tag.table.c.name.like( q + "%" ), + item_tag_assoc_class.table.c.user_id == user.id ) + order_by = [ func.count( "*" ).desc() ] group_by = item_tag_assoc_class.table.c.tag_id - # 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, limit=limit) - result_set = trans.sa_session.execute(query) - + query = select( columns=cols_to_select, + from_obj=from_obj, + whereclause=where_clause, + group_by=group_by, + order_by=order_by, + limit=limit ) + result_set = trans.sa_session.execute( query ) # Create and return autocomplete data. ac_data = "#Header|Your Tags\n" for row in result_set: - tag = self.tag_handler.get_tag_by_id(trans.sa_session, row[0]) - + tag = self.tag_handler.get_tag_by_id( trans, row[0] ) # Exclude tags that are already applied to the item. - if ( item is not None ) and ( self.tag_handler.item_has_tag( trans.get_user(), item, tag ) ): + if ( item is not None ) and ( self.tag_handler.item_has_tag( trans, trans.user, 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_names = self._get_usernames_for_tag( trans, trans.user, 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, q, limit, timestamp, user=None, item=None, item_class=None): - """Returns autocomplete data for tag values ordered from most frequently used to - least frequently used.""" - - tag_name_and_value = q.split(":") + def _get_tag_autocomplete_values( self, trans, q, limit, timestamp, user=None, item=None, item_class=None ): + """ + Returns autocomplete data for tag values ordered from most frequently used to + least frequently used. + """ + tag_name_and_value = q.split( ":" ) tag_name = tag_name_and_value[0] tag_value = tag_name_and_value[1] - tag = self.tag_handler.get_tag_by_name(trans.sa_session, tag_name) + tag = self.tag_handler.get_tag_by_name( trans, tag_name ) # Don't autocomplete if tag doesn't exist. if tag is None: return "" - # 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") + 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) - + 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_class.table ).join( model.Tag.table ) + cols_to_select = [ item_tag_assoc_class.table.c.value, func.count( '*' ) ] + from_obj = item_tag_assoc_class.table.join( item_class.table ).join( trans.app.model.Tag.table ) where_clause = and_( item_tag_assoc_class.table.c.user_id == user.id, - model.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 ] + trans.app.model.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 ] 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, limit=limit) - result_set = trans.sa_session.execute(query) - + query = select( columns=cols_to_select, + from_obj=from_obj, + whereclause=where_clause, + group_by=group_by, + order_by=order_by, + limit=limit ) + result_set = trans.sa_session.execute( query ) # Create and return autocomplete data. - ac_data = "#Header|Your Values for '%s'\n" % (tag_name) - tag_uname = self._get_usernames_for_tag(trans.sa_session, trans.get_user(), tag, item_class, item_tag_assoc_class)[0] + ac_data = "#Header|Your Values for '%s'\n" % ( tag_name ) + tag_uname = self._get_usernames_for_tag( trans, trans.user, tag, item_class, item_tag_assoc_class )[0] for row in result_set: ac_data += tag_uname + ":" + row[0] + "|" + row[0] + "\n" return ac_data - - def _get_usernames_for_tag(self, db_session, user, tag, item_class, item_tag_assoc_class): - """ Returns an ordered list of the user names for a tag; list is ordered from - most popular to least popular name.""" - + def _get_usernames_for_tag( self, trans, user, tag, item_class, item_tag_assoc_class ): + """ + Returns an ordered list of the user names for a tag; list is ordered from + most popular to least popular name. + """ # Build select stmt. - cols_to_select = [ item_tag_assoc_class.table.c.user_tname, func.count('*') ] + cols_to_select = [ item_tag_assoc_class.table.c.user_tname, func.count( '*' ) ] where_clause = and_( item_tag_assoc_class.table.c.user_id == user.id, item_tag_assoc_class.table.c.tag_id == tag.id ) group_by = item_tag_assoc_class.table.c.user_tname - order_by = [ func.count("*").desc() ] - + order_by = [ func.count( "*" ).desc() ] # Do query and get result set. - query = select(columns=cols_to_select, whereclause=where_clause, - group_by=group_by, order_by=order_by) - result_set = db_session.execute(query) - + query = select( columns=cols_to_select, + whereclause=where_clause, + group_by=group_by, + order_by=order_by ) + result_set = trans.sa_session.execute( query ) user_tag_names = list() for row in result_set: - user_tag_names.append(row[0]) - + user_tag_names.append( row[0] ) return user_tag_names - def _get_item( self, trans, item_class_name, id ): """ Get an item based on type and id. """ item_class = self.tag_handler.item_tag_assoc_info[item_class_name].item_class - item = trans.sa_session.query(item_class).filter("id=" + str(id))[0] + item = trans.sa_session.query( item_class ).filter( "id=" + str( id ) )[0] return item diff -r 4fb48981bdb0 -r e3167368345a lib/galaxy/web/controllers/user.py --- a/lib/galaxy/web/controllers/user.py Tue Apr 13 13:14:59 2010 -0400 +++ b/lib/galaxy/web/controllers/user.py Tue Apr 13 15:35:32 2010 -0400 @@ -89,7 +89,7 @@ else: refresh_frames = [ 'masthead', 'history' ] else: - refresh_frames = [] + refresh_frames = [ 'masthead' ] # Since logging an event requires a session, we'll log prior to ending the session trans.log_event( "User logged out" ) trans.handle_user_logout() diff -r 4fb48981bdb0 -r e3167368345a lib/galaxy/web/framework/helpers/grids.py --- a/lib/galaxy/web/framework/helpers/grids.py Tue Apr 13 13:14:59 2010 -0400 +++ b/lib/galaxy/web/framework/helpers/grids.py Tue Apr 13 15:35:32 2010 -0400 @@ -3,7 +3,7 @@ from galaxy.web.base import controller from galaxy.web.framework.helpers import iff -from galaxy.tags.tag_handler import TagHandler +from galaxy.tags.tag_handler import GalaxyTagHandler from galaxy.web import url_for from galaxy.util.json import from_json_string, to_json_string from galaxy.util.odict import odict @@ -399,7 +399,7 @@ return query def get_filter( self, user, column_filter ): # Parse filter to extract multiple tags. - tag_handler = TagHandler() + tag_handler = GalaxyTagHandler() if isinstance( column_filter, list ): # Collapse list of tags into a single string; this is redundant but effective. TODO: fix this by iterating over tags. column_filter = ",".join( column_filter ) @@ -421,7 +421,7 @@ in_form=True, input_size="20", tag_click_fn="add_tag_to_grid_filter", use_toggle_link=True ) def get_filter( self, user, column_filter ): # Parse filter to extract multiple tags. - tag_handler = TagHandler() + tag_handler = GalaxyTagHandler() if isinstance( column_filter, list ): # Collapse list of tags into a single string; this is redundant but effective. TODO: fix this by iterating over tags. column_filter = ",".join( column_filter ) diff -r 4fb48981bdb0 -r e3167368345a templates/tagging_common.mako --- a/templates/tagging_common.mako Tue Apr 13 13:14:59 2010 -0400 +++ b/templates/tagging_common.mako Tue Apr 13 15:35:32 2010 -0400 @@ -4,10 +4,7 @@ from random import random from sys import maxint from math import floor - from galaxy.tags.tag_handler import TagHandler from galaxy.model import Tag, ItemTagAssociation - - tag_handler = TagHandler() %> ## Render a tagging element if there is a tagged_item. @@ -92,7 +89,7 @@ ## Build HTML. <% elt_id = int ( floor ( random()*maxint ) ) - community_tags = tag_handler.get_community_tags(trans.sa_session, tagged_item, 10) + community_tags = trans.app.tag_handler.get_community_tags( trans, item=tagged_item, limit=10 ) %> ${self.render_tagging_element_html(elt_id=elt_id, tags=community_tags, use_toggle_link=use_toggle_link, editable=False, tag_type="community")} diff -r 4fb48981bdb0 -r e3167368345a templates/user/index.mako --- a/templates/user/index.mako Tue Apr 13 13:14:59 2010 -0400 +++ b/templates/user/index.mako Tue Apr 13 15:35:32 2010 -0400 @@ -13,10 +13,9 @@ <li><a href="${h.url_for( action='show_info' )}">${_('Manage your information')}</a></li> <li><a href="${h.url_for( action='set_default_permissions' )}">${_('Change default permissions')}</a> for new histories</li> %endif - <li><a href="${h.url_for( action='logout' )}">${_('Logout')}</a></li> </ul> %else: - %if not msg: + %if not message: <p>${n_('You are currently not logged in.')}</p> %endif <ul> diff -r 4fb48981bdb0 -r e3167368345a test/base/twilltestcase.py --- a/test/base/twilltestcase.py Tue Apr 13 13:14:59 2010 -0400 +++ b/test/base/twilltestcase.py Tue Apr 13 15:35:32 2010 -0400 @@ -585,6 +585,16 @@ except: pass self.home() + def check_hda_attribute_info( self, hda_id, check_str1='', check_str2='', check_str3='', check_str4='' ): + """Edit history_dataset_association attribute information""" + if check_str1: + self.check_page_for_string( check_str1 ) + if check_str2: + self.check_page_for_string( check_str2 ) + if check_str3: + self.check_page_for_string( check_str3 ) + if check_str4: + self.check_page_for_string( check_str4 ) def auto_detect_metadata( self, hda_id ): """Auto-detect history_dataset_association metadata""" self.home() @@ -2050,3 +2060,9 @@ else: break self.assertNotEqual(count, maxiter) + + # Tests associated with tags + def add_tag( self, item_id, item_class, context, new_tag, check_str='' ): + self.visit_url( "%s/tag/add_tag_async?item_id=%s&item_class=%s&context=%s&new_tag=%s" % \ + ( self.url, item_id, item_class, context, new_tag ) ) + \ No newline at end of file diff -r 4fb48981bdb0 -r e3167368345a test/functional/test_tags.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/functional/test_tags.py Tue Apr 13 15:35:32 2010 -0400 @@ -0,0 +1,63 @@ +from base.twilltestcase import * +from base.test_db_util import * + +class TestTags( TwillTestCase ): + # TODO: Add more functional test coverage for tags + def test_000_initiate_users( self ): + """Ensuring all required user accounts exist""" + self.logout() + self.login( email='test1@bx.psu.edu', username='regular-user1' ) + global regular_user1 + regular_user1 = get_user( 'test1@bx.psu.edu' ) + assert regular_user1 is not None, 'Problem retrieving user with email "test1@bx.psu.edu" from the database' + global regular_user1_private_role + regular_user1_private_role = get_private_role( regular_user1 ) + self.logout() + self.login( email='test2@bx.psu.edu', username='regular-user2' ) + global regular_user2 + regular_user2 = get_user( 'test2@bx.psu.edu' ) + assert regular_user2 is not None, 'Problem retrieving user with email "test2@bx.psu.edu" from the database' + global regular_user2_private_role + regular_user2_private_role = get_private_role( regular_user2 ) + self.logout() + self.login( email='test3@bx.psu.edu', username='regular-user3' ) + global regular_user3 + regular_user3 = get_user( 'test3@bx.psu.edu' ) + assert regular_user3 is not None, 'Problem retrieving user with email "test3@bx.psu.edu" from the database' + global regular_user3_private_role + regular_user3_private_role = get_private_role( regular_user3 ) + self.logout() + self.login( email='test@bx.psu.edu', username='admin-user' ) + global admin_user + admin_user = get_user( 'test@bx.psu.edu' ) + assert admin_user is not None, 'Problem retrieving user with email "test@bx.psu.edu" from the database' + global admin_user_private_role + admin_user_private_role = get_private_role( admin_user ) + def test_005_add_tag_to_history( self ): + """Testing adding a tag to a history""" + # Logged in as admin_user + # Create a new, empty history named anonymous + name = 'anonymous' + self.new_history( name=name ) + global history1 + history1 = get_latest_history_for_user( admin_user ) + self.add_tag( self.security.encode_id( history1.id ), + 'History', + 'history.mako', + 'hello' ) + self.check_history_for_string( 'tags : {"hello"' ) + def test_010_add_tag_to_history_item( self ): + """Testing adding a tag to a history item""" + # Logged in as admin_user + self.upload_file( '1.bed' ) + latest_hda = get_latest_hda() + self.add_tag( self.security.encode_id( latest_hda.id ), + 'HistoryDatasetAssociation', + 'edit_attributes.mako', + 'goodbye' ) + self.check_hda_attribute_info( 'tags : {"goodbye"' ) + def test_999_reset_data_for_later_test_runs( self ): + """Reseting data to enable later test runs to to be valid""" + # logged in as admin_user + # Delete histories + self.delete_history( id=self.security.encode_id( history1.id ) )