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 |
---|