[2] | 1 | """ |
---|
| 2 | Tags Controller: handles tagging/untagging of entities and provides autocomplete support. |
---|
| 3 | """ |
---|
| 4 | import logging |
---|
| 5 | from galaxy.web.base.controller import * |
---|
| 6 | from sqlalchemy.sql.expression import func, and_ |
---|
| 7 | from sqlalchemy.sql import select |
---|
| 8 | |
---|
| 9 | log = logging.getLogger( __name__ ) |
---|
| 10 | |
---|
| 11 | class TagsController ( BaseController ): |
---|
| 12 | def __init__( self, app ): |
---|
| 13 | BaseController.__init__( self, app ) |
---|
| 14 | self.tag_handler = app.tag_handler |
---|
| 15 | @web.expose |
---|
| 16 | @web.require_login( "edit item tags" ) |
---|
| 17 | def get_tagging_elt_async( self, trans, item_id, item_class, elt_context="" ): |
---|
| 18 | """ Returns HTML for editing an item's tags. """ |
---|
| 19 | item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) |
---|
| 20 | if not item: |
---|
| 21 | return trans.show_error_message( "No item of class %s with id %s " % ( item_class, item_id ) ) |
---|
| 22 | return trans.fill_template( "/tagging_common.mako", |
---|
| 23 | tag_type="individual", |
---|
| 24 | user=trans.user, |
---|
| 25 | tagged_item=item, |
---|
| 26 | elt_context=elt_context, |
---|
| 27 | in_form=False, |
---|
| 28 | input_size="22", |
---|
| 29 | tag_click_fn="default_tag_click_fn", |
---|
| 30 | use_toggle_link=False ) |
---|
| 31 | @web.expose |
---|
| 32 | @web.require_login( "add tag to an item" ) |
---|
| 33 | def add_tag_async( self, trans, item_id=None, item_class=None, new_tag=None, context=None ): |
---|
| 34 | """ Add tag to an item. """ |
---|
| 35 | # Apply tag. |
---|
| 36 | item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) |
---|
| 37 | user = trans.user |
---|
| 38 | self.tag_handler.apply_item_tags( trans, user, item, new_tag.encode( 'utf-8' ) ) |
---|
| 39 | trans.sa_session.flush() |
---|
| 40 | # Log. |
---|
| 41 | params = dict( item_id=item.id, item_class=item_class, tag=new_tag ) |
---|
| 42 | trans.log_action( user, unicode( "tag" ), context, params ) |
---|
| 43 | @web.expose |
---|
| 44 | @web.require_login( "remove tag from an item" ) |
---|
| 45 | def remove_tag_async( self, trans, item_id=None, item_class=None, tag_name=None, context=None ): |
---|
| 46 | """ Remove tag from an item. """ |
---|
| 47 | # Remove tag. |
---|
| 48 | item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) |
---|
| 49 | user = trans.user |
---|
| 50 | self.tag_handler.remove_item_tag( trans, user, item, tag_name.encode( 'utf-8' ) ) |
---|
| 51 | trans.sa_session.flush() |
---|
| 52 | # Log. |
---|
| 53 | params = dict( item_id=item.id, item_class=item_class, tag=tag_name ) |
---|
| 54 | trans.log_action( user, unicode( "untag" ), context, params ) |
---|
| 55 | # Retag an item. All previous tags are deleted and new tags are applied. |
---|
| 56 | #@web.expose |
---|
| 57 | @web.require_login( "Apply a new set of tags to an item; previous tags are deleted." ) |
---|
| 58 | def retag_async( self, trans, item_id=None, item_class=None, new_tags=None ): |
---|
| 59 | """ Apply a new set of tags to an item; previous tags are deleted. """ |
---|
| 60 | # Apply tags. |
---|
| 61 | item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) |
---|
| 62 | user = trans.user |
---|
| 63 | self.tag_handler.delete_item_tags( trans, item ) |
---|
| 64 | self.tag_handler.apply_item_tags( trans, user, item, new_tags.encode( 'utf-8' ) ) |
---|
| 65 | trans.sa_session.flush() |
---|
| 66 | @web.expose |
---|
| 67 | @web.require_login( "get autocomplete data for an item's tags" ) |
---|
| 68 | def tag_autocomplete_data( self, trans, q=None, limit=None, timestamp=None, item_id=None, item_class=None ): |
---|
| 69 | """ Get autocomplete data for an item's tags. """ |
---|
| 70 | # Get item, do security check, and get autocomplete data. |
---|
| 71 | item = None |
---|
| 72 | if item_id is not None: |
---|
| 73 | item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) |
---|
| 74 | user = trans.user |
---|
| 75 | item_class = self.get_class( trans, item_class ) |
---|
| 76 | q = q.encode( 'utf-8' ) |
---|
| 77 | if q.find( ":" ) == -1: |
---|
| 78 | return self._get_tag_autocomplete_names( trans, q, limit, timestamp, user, item, item_class ) |
---|
| 79 | else: |
---|
| 80 | return self._get_tag_autocomplete_values( trans, q, limit, timestamp, user, item, item_class ) |
---|
| 81 | def _get_tag_autocomplete_names( self, trans, q, limit, timestamp, user=None, item=None, item_class=None ): |
---|
| 82 | """ |
---|
| 83 | Returns autocomplete data for tag names ordered from most frequently used to |
---|
| 84 | least frequently used. |
---|
| 85 | """ |
---|
| 86 | # Get user's item tags and usage counts. |
---|
| 87 | # Get item's class object and item-tag association class. |
---|
| 88 | if item is None and item_class is None: |
---|
| 89 | raise RuntimeError( "Both item and item_class cannot be None" ) |
---|
| 90 | elif item is not None: |
---|
| 91 | item_class = item.__class__ |
---|
| 92 | item_tag_assoc_class = self.tag_handler.get_tag_assoc_class( item_class ) |
---|
| 93 | # Build select statement. |
---|
| 94 | cols_to_select = [ item_tag_assoc_class.table.c.tag_id, func.count( '*' ) ] |
---|
| 95 | from_obj = item_tag_assoc_class.table.join( item_class.table ).join( trans.app.model.Tag.table ) |
---|
| 96 | where_clause = and_( trans.app.model.Tag.table.c.name.like( q + "%" ), |
---|
| 97 | item_tag_assoc_class.table.c.user_id == user.id ) |
---|
| 98 | order_by = [ func.count( "*" ).desc() ] |
---|
| 99 | group_by = item_tag_assoc_class.table.c.tag_id |
---|
| 100 | # Do query and get result set. |
---|
| 101 | query = select( columns=cols_to_select, |
---|
| 102 | from_obj=from_obj, |
---|
| 103 | whereclause=where_clause, |
---|
| 104 | group_by=group_by, |
---|
| 105 | order_by=order_by, |
---|
| 106 | limit=limit ) |
---|
| 107 | result_set = trans.sa_session.execute( query ) |
---|
| 108 | # Create and return autocomplete data. |
---|
| 109 | ac_data = "#Header|Your Tags\n" |
---|
| 110 | for row in result_set: |
---|
| 111 | tag = self.tag_handler.get_tag_by_id( trans, row[0] ) |
---|
| 112 | # Exclude tags that are already applied to the item. |
---|
| 113 | if ( item is not None ) and ( self.tag_handler.item_has_tag( trans, trans.user, item, tag ) ): |
---|
| 114 | continue |
---|
| 115 | # Add tag to autocomplete data. Use the most frequent name that user |
---|
| 116 | # has employed for the tag. |
---|
| 117 | tag_names = self._get_usernames_for_tag( trans, trans.user, tag, item_class, item_tag_assoc_class ) |
---|
| 118 | ac_data += tag_names[0] + "|" + tag_names[0] + "\n" |
---|
| 119 | return ac_data |
---|
| 120 | def _get_tag_autocomplete_values( self, trans, q, limit, timestamp, user=None, item=None, item_class=None ): |
---|
| 121 | """ |
---|
| 122 | Returns autocomplete data for tag values ordered from most frequently used to |
---|
| 123 | least frequently used. |
---|
| 124 | """ |
---|
| 125 | tag_name_and_value = q.split( ":" ) |
---|
| 126 | tag_name = tag_name_and_value[0] |
---|
| 127 | tag_value = tag_name_and_value[1] |
---|
| 128 | tag = self.tag_handler.get_tag_by_name( trans, tag_name ) |
---|
| 129 | # Don't autocomplete if tag doesn't exist. |
---|
| 130 | if tag is None: |
---|
| 131 | return "" |
---|
| 132 | # Get item's class object and item-tag association class. |
---|
| 133 | if item is None and item_class is None: |
---|
| 134 | raise RuntimeError( "Both item and item_class cannot be None" ) |
---|
| 135 | elif item is not None: |
---|
| 136 | item_class = item.__class__ |
---|
| 137 | item_tag_assoc_class = self.tag_handler.get_tag_assoc_class( item_class ) |
---|
| 138 | # Build select statement. |
---|
| 139 | cols_to_select = [ item_tag_assoc_class.table.c.value, func.count( '*' ) ] |
---|
| 140 | from_obj = item_tag_assoc_class.table.join( item_class.table ).join( trans.app.model.Tag.table ) |
---|
| 141 | where_clause = and_( item_tag_assoc_class.table.c.user_id == user.id, |
---|
| 142 | trans.app.model.Tag.table.c.id == tag.id, |
---|
| 143 | item_tag_assoc_class.table.c.value.like( tag_value + "%" ) ) |
---|
| 144 | order_by = [ func.count("*").desc(), item_tag_assoc_class.table.c.value ] |
---|
| 145 | group_by = item_tag_assoc_class.table.c.value |
---|
| 146 | # Do query and get result set. |
---|
| 147 | query = select( columns=cols_to_select, |
---|
| 148 | from_obj=from_obj, |
---|
| 149 | whereclause=where_clause, |
---|
| 150 | group_by=group_by, |
---|
| 151 | order_by=order_by, |
---|
| 152 | limit=limit ) |
---|
| 153 | result_set = trans.sa_session.execute( query ) |
---|
| 154 | # Create and return autocomplete data. |
---|
| 155 | ac_data = "#Header|Your Values for '%s'\n" % ( tag_name ) |
---|
| 156 | tag_uname = self._get_usernames_for_tag( trans, trans.user, tag, item_class, item_tag_assoc_class )[0] |
---|
| 157 | for row in result_set: |
---|
| 158 | ac_data += tag_uname + ":" + row[0] + "|" + row[0] + "\n" |
---|
| 159 | return ac_data |
---|
| 160 | def _get_usernames_for_tag( self, trans, user, tag, item_class, item_tag_assoc_class ): |
---|
| 161 | """ |
---|
| 162 | Returns an ordered list of the user names for a tag; list is ordered from |
---|
| 163 | most popular to least popular name. |
---|
| 164 | """ |
---|
| 165 | # Build select stmt. |
---|
| 166 | cols_to_select = [ item_tag_assoc_class.table.c.user_tname, func.count( '*' ) ] |
---|
| 167 | where_clause = and_( item_tag_assoc_class.table.c.user_id == user.id, |
---|
| 168 | item_tag_assoc_class.table.c.tag_id == tag.id ) |
---|
| 169 | group_by = item_tag_assoc_class.table.c.user_tname |
---|
| 170 | order_by = [ func.count( "*" ).desc() ] |
---|
| 171 | # Do query and get result set. |
---|
| 172 | query = select( columns=cols_to_select, |
---|
| 173 | whereclause=where_clause, |
---|
| 174 | group_by=group_by, |
---|
| 175 | order_by=order_by ) |
---|
| 176 | result_set = trans.sa_session.execute( query ) |
---|
| 177 | user_tag_names = list() |
---|
| 178 | for row in result_set: |
---|
| 179 | user_tag_names.append( row[0] ) |
---|
| 180 | return user_tag_names |
---|
| 181 | def _get_item( self, trans, item_class_name, id ): |
---|
| 182 | """ Get an item based on type and id. """ |
---|
| 183 | item_class = self.tag_handler.item_tag_assoc_info[item_class_name].item_class |
---|
| 184 | item = trans.sa_session.query( item_class ).filter( "id=" + str( id ) )[0] |
---|
| 185 | return item |
---|