1 | from galaxy.web.base.controller import * |
---|
2 | from galaxy.web.framework.helpers import time_ago, iff, grids |
---|
3 | from galaxy import model, util |
---|
4 | from galaxy.util.odict import odict |
---|
5 | from galaxy.model.mapping import desc |
---|
6 | from galaxy.model.orm import * |
---|
7 | from galaxy.model.item_attrs import * |
---|
8 | from galaxy.util.json import * |
---|
9 | from galaxy.util.sanitize_html import sanitize_html |
---|
10 | from galaxy.tools.parameters.basic import UnvalidatedValue |
---|
11 | from galaxy.tools.actions import upload_common |
---|
12 | from galaxy.tags.tag_handler import GalaxyTagHandler |
---|
13 | from sqlalchemy.sql.expression import ClauseElement |
---|
14 | import webhelpers, logging, operator, os, tempfile, subprocess, shutil, tarfile |
---|
15 | from datetime import datetime |
---|
16 | from cgi import escape |
---|
17 | |
---|
18 | log = logging.getLogger( __name__ ) |
---|
19 | |
---|
20 | class NameColumn( grids.TextColumn ): |
---|
21 | def get_value( self, trans, grid, history ): |
---|
22 | return history.get_display_name() |
---|
23 | |
---|
24 | class HistoryListGrid( grids.Grid ): |
---|
25 | # Custom column types |
---|
26 | class DatasetsByStateColumn( grids.GridColumn ): |
---|
27 | def get_value( self, trans, grid, history ): |
---|
28 | rval = [] |
---|
29 | for state in ( 'ok', 'running', 'queued', 'error' ): |
---|
30 | total = sum( 1 for d in history.active_datasets if d.state == state ) |
---|
31 | if total: |
---|
32 | rval.append( '<div class="count-box state-color-%s">%s</div>' % ( state, total ) ) |
---|
33 | else: |
---|
34 | rval.append( '' ) |
---|
35 | return rval |
---|
36 | |
---|
37 | # Grid definition |
---|
38 | title = "Saved Histories" |
---|
39 | model_class = model.History |
---|
40 | template='/history/grid.mako' |
---|
41 | default_sort_key = "-update_time" |
---|
42 | columns = [ |
---|
43 | NameColumn( "Name", key="name", |
---|
44 | link=( lambda history: iff( history.deleted, None, dict( operation="Switch", id=history.id ) ) ), |
---|
45 | attach_popup=True, filterable="advanced" ), |
---|
46 | DatasetsByStateColumn( "Datasets (by state)", ncells=4 ), |
---|
47 | grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.HistoryTagAssociation, \ |
---|
48 | filterable="advanced", grid_name="HistoryListGrid" ), |
---|
49 | grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False ), |
---|
50 | grids.GridColumn( "Created", key="create_time", format=time_ago ), |
---|
51 | grids.GridColumn( "Last Updated", key="update_time", format=time_ago ), |
---|
52 | # Columns that are valid for filtering but are not visible. |
---|
53 | grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ) |
---|
54 | ] |
---|
55 | columns.append( |
---|
56 | grids.MulticolFilterColumn( |
---|
57 | "Search", |
---|
58 | cols_to_filter=[ columns[0], columns[2] ], |
---|
59 | key="free-text-search", visible=False, filterable="standard" ) |
---|
60 | ) |
---|
61 | |
---|
62 | operations = [ |
---|
63 | grids.GridOperation( "Switch", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False ), |
---|
64 | grids.GridOperation( "Share or Publish", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False ), |
---|
65 | grids.GridOperation( "Rename", condition=( lambda item: not item.deleted ), async_compatible=False ), |
---|
66 | grids.GridOperation( "Delete", condition=( lambda item: not item.deleted ), async_compatible=True ), |
---|
67 | grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ), async_compatible=True ), |
---|
68 | ] |
---|
69 | standard_filters = [ |
---|
70 | grids.GridColumnFilter( "Active", args=dict( deleted=False ) ), |
---|
71 | grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ), |
---|
72 | grids.GridColumnFilter( "All", args=dict( deleted='All' ) ), |
---|
73 | ] |
---|
74 | default_filter = dict( name="All", deleted="False", tags="All", sharing="All" ) |
---|
75 | num_rows_per_page = 50 |
---|
76 | preserve_state = False |
---|
77 | use_async = True |
---|
78 | use_paging = True |
---|
79 | def get_current_item( self, trans, **kwargs ): |
---|
80 | return trans.get_history() |
---|
81 | def apply_query_filter( self, trans, query, **kwargs ): |
---|
82 | return query.filter_by( user=trans.user, purged=False ) |
---|
83 | |
---|
84 | class SharedHistoryListGrid( grids.Grid ): |
---|
85 | # Custom column types |
---|
86 | class DatasetsByStateColumn( grids.GridColumn ): |
---|
87 | def get_value( self, trans, grid, history ): |
---|
88 | rval = [] |
---|
89 | for state in ( 'ok', 'running', 'queued', 'error' ): |
---|
90 | total = sum( 1 for d in history.active_datasets if d.state == state ) |
---|
91 | if total: |
---|
92 | rval.append( '<div class="count-box state-color-%s">%s</div>' % ( state, total ) ) |
---|
93 | else: |
---|
94 | rval.append( '' ) |
---|
95 | return rval |
---|
96 | class SharedByColumn( grids.GridColumn ): |
---|
97 | def get_value( self, trans, grid, history ): |
---|
98 | return history.user.email |
---|
99 | # Grid definition |
---|
100 | title = "Histories shared with you by others" |
---|
101 | model_class = model.History |
---|
102 | default_sort_key = "-update_time" |
---|
103 | default_filter = {} |
---|
104 | columns = [ |
---|
105 | grids.GridColumn( "Name", key="name", attach_popup=True ), # link=( lambda item: dict( operation="View", id=item.id ) ), attach_popup=True ), |
---|
106 | DatasetsByStateColumn( "Datasets (by state)", ncells=4 ), |
---|
107 | grids.GridColumn( "Created", key="create_time", format=time_ago ), |
---|
108 | grids.GridColumn( "Last Updated", key="update_time", format=time_ago ), |
---|
109 | SharedByColumn( "Shared by", key="user_id" ) |
---|
110 | ] |
---|
111 | operations = [ |
---|
112 | grids.GridOperation( "View", allow_multiple=False, target="_top" ), |
---|
113 | grids.GridOperation( "Clone" ), |
---|
114 | grids.GridOperation( "Unshare" ) |
---|
115 | ] |
---|
116 | standard_filters = [] |
---|
117 | def build_initial_query( self, trans, **kwargs ): |
---|
118 | return trans.sa_session.query( self.model_class ).join( 'users_shared_with' ) |
---|
119 | def apply_query_filter( self, trans, query, **kwargs ): |
---|
120 | return query.filter( model.HistoryUserShareAssociation.user == trans.user ) |
---|
121 | |
---|
122 | class HistoryAllPublishedGrid( grids.Grid ): |
---|
123 | class NameURLColumn( grids.PublicURLColumn, NameColumn ): |
---|
124 | pass |
---|
125 | |
---|
126 | title = "Published Histories" |
---|
127 | model_class = model.History |
---|
128 | default_sort_key = "update_time" |
---|
129 | default_filter = dict( public_url="All", username="All", tags="All" ) |
---|
130 | use_async = True |
---|
131 | columns = [ |
---|
132 | NameURLColumn( "Name", key="name", filterable="advanced" ), |
---|
133 | grids.OwnerAnnotationColumn( "Annotation", key="annotation", model_annotation_association_class=model.HistoryAnnotationAssociation, filterable="advanced" ), |
---|
134 | grids.OwnerColumn( "Owner", key="owner", model_class=model.User, filterable="advanced" ), |
---|
135 | grids.CommunityRatingColumn( "Community Rating", key="rating" ), |
---|
136 | grids.CommunityTagsColumn( "Community Tags", key="tags", model_tag_association_class=model.HistoryTagAssociation, filterable="advanced", grid_name="PublicHistoryListGrid" ), |
---|
137 | grids.ReverseSortColumn( "Last Updated", key="update_time", format=time_ago ) |
---|
138 | ] |
---|
139 | columns.append( |
---|
140 | grids.MulticolFilterColumn( |
---|
141 | "Search", |
---|
142 | cols_to_filter=[ columns[0], columns[1], columns[2] ], |
---|
143 | key="free-text-search", visible=False, filterable="standard" ) |
---|
144 | ) |
---|
145 | operations = [] |
---|
146 | def build_initial_query( self, trans, **kwargs ): |
---|
147 | # Join so that searching history.user makes sense. |
---|
148 | return trans.sa_session.query( self.model_class ).join( model.User.table ) |
---|
149 | def apply_query_filter( self, trans, query, **kwargs ): |
---|
150 | # A public history is published, has a slug, and is not deleted. |
---|
151 | return query.filter( self.model_class.published == True ).filter( self.model_class.slug != None ).filter( self.model_class.deleted == False ) |
---|
152 | |
---|
153 | class HistoryController( BaseController, Sharable, UsesAnnotations, UsesItemRatings, UsesHistory ): |
---|
154 | @web.expose |
---|
155 | def index( self, trans ): |
---|
156 | return "" |
---|
157 | @web.expose |
---|
158 | def list_as_xml( self, trans ): |
---|
159 | """XML history list for functional tests""" |
---|
160 | trans.response.set_content_type( 'text/xml' ) |
---|
161 | return trans.fill_template( "/history/list_as_xml.mako" ) |
---|
162 | |
---|
163 | stored_list_grid = HistoryListGrid() |
---|
164 | shared_list_grid = SharedHistoryListGrid() |
---|
165 | published_list_grid = HistoryAllPublishedGrid() |
---|
166 | |
---|
167 | @web.expose |
---|
168 | def list_published( self, trans, **kwargs ): |
---|
169 | grid = self.published_list_grid( trans, **kwargs ) |
---|
170 | if 'async' in kwargs: |
---|
171 | return grid |
---|
172 | else: |
---|
173 | # Render grid wrapped in panels |
---|
174 | return trans.fill_template( "history/list_published.mako", grid=grid ) |
---|
175 | |
---|
176 | @web.expose |
---|
177 | @web.require_login( "work with multiple histories" ) |
---|
178 | def list( self, trans, **kwargs ): |
---|
179 | """List all available histories""" |
---|
180 | current_history = trans.get_history() |
---|
181 | status = message = None |
---|
182 | if 'operation' in kwargs: |
---|
183 | operation = kwargs['operation'].lower() |
---|
184 | if operation == "share or publish": |
---|
185 | return self.sharing( trans, **kwargs ) |
---|
186 | if operation == "rename" and kwargs.get('id', None): # Don't call rename if no ids |
---|
187 | if 'name' in kwargs: |
---|
188 | del kwargs['name'] # Remove ajax name param that rename method uses |
---|
189 | return self.rename( trans, **kwargs ) |
---|
190 | history_ids = util.listify( kwargs.get( 'id', [] ) ) |
---|
191 | # Display no message by default |
---|
192 | status, message = None, None |
---|
193 | refresh_history = False |
---|
194 | # Load the histories and ensure they all belong to the current user |
---|
195 | histories = [] |
---|
196 | for history_id in history_ids: |
---|
197 | history = self.get_history( trans, history_id ) |
---|
198 | if history: |
---|
199 | # Ensure history is owned by current user |
---|
200 | if history.user_id != None and trans.user: |
---|
201 | assert trans.user.id == history.user_id, "History does not belong to current user" |
---|
202 | histories.append( history ) |
---|
203 | else: |
---|
204 | log.warn( "Invalid history id '%r' passed to list", history_id ) |
---|
205 | if histories: |
---|
206 | if operation == "switch": |
---|
207 | status, message = self._list_switch( trans, histories ) |
---|
208 | # Current history changed, refresh history frame |
---|
209 | trans.template_context['refresh_frames'] = ['history'] |
---|
210 | elif operation == "delete": |
---|
211 | status, message = self._list_delete( trans, histories ) |
---|
212 | if current_history in histories: |
---|
213 | # Deleted the current history, so a new, empty history was |
---|
214 | # created automatically, and we need to refresh the history frame |
---|
215 | trans.template_context['refresh_frames'] = ['history'] |
---|
216 | elif operation == "undelete": |
---|
217 | status, message = self._list_undelete( trans, histories ) |
---|
218 | elif operation == "unshare": |
---|
219 | for history in histories: |
---|
220 | for husa in trans.sa_session.query( trans.app.model.HistoryUserShareAssociation ) \ |
---|
221 | .filter_by( history=history ): |
---|
222 | trans.sa_session.delete( husa ) |
---|
223 | elif operation == "enable import via link": |
---|
224 | for history in histories: |
---|
225 | if not history.importable: |
---|
226 | self._make_item_importable( trans.sa_session, history ) |
---|
227 | elif operation == "disable import via link": |
---|
228 | if history_ids: |
---|
229 | histories = [ self.get_history( trans, history_id ) for history_id in history_ids ] |
---|
230 | for history in histories: |
---|
231 | if history.importable: |
---|
232 | history.importable = False |
---|
233 | trans.sa_session.flush() |
---|
234 | # Render the list view |
---|
235 | return self.stored_list_grid( trans, status=status, message=message, **kwargs ) |
---|
236 | def _list_delete( self, trans, histories ): |
---|
237 | """Delete histories""" |
---|
238 | n_deleted = 0 |
---|
239 | deleted_current = False |
---|
240 | message_parts = [] |
---|
241 | for history in histories: |
---|
242 | if history.users_shared_with: |
---|
243 | message_parts.append( "History (%s) has been shared with others, unshare it before deleting it. " % history.name ) |
---|
244 | elif not history.deleted: |
---|
245 | # We'll not eliminate any DefaultHistoryPermissions in case we undelete the history later |
---|
246 | history.deleted = True |
---|
247 | # If deleting the current history, make a new current. |
---|
248 | if history == trans.get_history(): |
---|
249 | deleted_current = True |
---|
250 | trans.new_history() |
---|
251 | trans.log_event( "History (%s) marked as deleted" % history.name ) |
---|
252 | n_deleted += 1 |
---|
253 | status = SUCCESS |
---|
254 | if n_deleted: |
---|
255 | message_parts.append( "Deleted %d %s. " % ( n_deleted, iff( n_deleted != 1, "histories", "history" ) ) ) |
---|
256 | if deleted_current: |
---|
257 | message_parts.append( "Your active history was deleted, a new empty history is now active. " ) |
---|
258 | status = INFO |
---|
259 | return ( status, " ".join( message_parts ) ) |
---|
260 | def _list_undelete( self, trans, histories ): |
---|
261 | """Undelete histories""" |
---|
262 | n_undeleted = 0 |
---|
263 | n_already_purged = 0 |
---|
264 | for history in histories: |
---|
265 | if history.purged: |
---|
266 | n_already_purged += 1 |
---|
267 | if history.deleted: |
---|
268 | history.deleted = False |
---|
269 | if not history.default_permissions: |
---|
270 | # For backward compatibility - for a while we were deleting all DefaultHistoryPermissions on |
---|
271 | # the history when we deleted the history. We are no longer doing this. |
---|
272 | # Need to add default DefaultHistoryPermissions in case they were deleted when the history was deleted |
---|
273 | default_action = trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS |
---|
274 | private_user_role = trans.app.security_agent.get_private_user_role( history.user ) |
---|
275 | default_permissions = {} |
---|
276 | default_permissions[ default_action ] = [ private_user_role ] |
---|
277 | trans.app.security_agent.history_set_default_permissions( history, default_permissions ) |
---|
278 | n_undeleted += 1 |
---|
279 | trans.log_event( "History (%s) %d marked as undeleted" % ( history.name, history.id ) ) |
---|
280 | status = SUCCESS |
---|
281 | message_parts = [] |
---|
282 | if n_undeleted: |
---|
283 | message_parts.append( "Undeleted %d %s. " % ( n_undeleted, iff( n_undeleted != 1, "histories", "history" ) ) ) |
---|
284 | if n_already_purged: |
---|
285 | message_parts.append( "%d histories have already been purged and cannot be undeleted." % n_already_purged ) |
---|
286 | status = WARNING |
---|
287 | return status, "".join( message_parts ) |
---|
288 | def _list_switch( self, trans, histories ): |
---|
289 | """Switch to a new different history""" |
---|
290 | new_history = histories[0] |
---|
291 | galaxy_session = trans.get_galaxy_session() |
---|
292 | try: |
---|
293 | association = trans.sa_session.query( trans.app.model.GalaxySessionToHistoryAssociation ) \ |
---|
294 | .filter_by( session_id=galaxy_session.id, history_id=trans.security.decode_id( new_history.id ) ) \ |
---|
295 | .first() |
---|
296 | except: |
---|
297 | association = None |
---|
298 | new_history.add_galaxy_session( galaxy_session, association=association ) |
---|
299 | trans.sa_session.add( new_history ) |
---|
300 | trans.sa_session.flush() |
---|
301 | trans.set_history( new_history ) |
---|
302 | # No message |
---|
303 | return None, None |
---|
304 | |
---|
305 | @web.expose |
---|
306 | @web.require_login( "work with shared histories" ) |
---|
307 | def list_shared( self, trans, **kwargs ): |
---|
308 | """List histories shared with current user by others""" |
---|
309 | msg = util.restore_text( kwargs.get( 'msg', '' ) ) |
---|
310 | status = message = None |
---|
311 | if 'operation' in kwargs: |
---|
312 | ids = util.listify( kwargs.get( 'id', [] ) ) |
---|
313 | operation = kwargs['operation'].lower() |
---|
314 | if operation == "view": |
---|
315 | # Display history. |
---|
316 | history = self.get_history( trans, ids[0], False) |
---|
317 | return self.display_by_username_and_slug( trans, history.user.username, history.slug ) |
---|
318 | elif operation == "clone": |
---|
319 | if not ids: |
---|
320 | message = "Select a history to clone" |
---|
321 | return self.shared_list_grid( trans, status='error', message=message, **kwargs ) |
---|
322 | # When cloning shared histories, only copy active datasets |
---|
323 | new_kwargs = { 'clone_choice' : 'active' } |
---|
324 | return self.clone( trans, ids, **new_kwargs ) |
---|
325 | elif operation == 'unshare': |
---|
326 | if not ids: |
---|
327 | message = "Select a history to unshare" |
---|
328 | return self.shared_list_grid( trans, status='error', message=message, **kwargs ) |
---|
329 | histories = [ self.get_history( trans, history_id ) for history_id in ids ] |
---|
330 | for history in histories: |
---|
331 | # Current user is the user with which the histories were shared |
---|
332 | association = trans.sa_session.query( trans.app.model.HistoryUserShareAssociation ).filter_by( user=trans.user, history=history ).one() |
---|
333 | trans.sa_session.delete( association ) |
---|
334 | trans.sa_session.flush() |
---|
335 | message = "Unshared %d shared histories" % len( ids ) |
---|
336 | status = 'done' |
---|
337 | # Render the list view |
---|
338 | return self.shared_list_grid( trans, status=status, message=message, **kwargs ) |
---|
339 | |
---|
340 | @web.expose |
---|
341 | def display_structured( self, trans, id=None ): |
---|
342 | """ |
---|
343 | Display a history as a nested structure showing the jobs and workflow |
---|
344 | invocations that created each dataset (if any). |
---|
345 | """ |
---|
346 | # Get history |
---|
347 | if id is None: |
---|
348 | id = trans.history.id |
---|
349 | else: |
---|
350 | id = trans.security.decode_id( id ) |
---|
351 | # Expunge history from the session to allow us to force a reload |
---|
352 | # with a bunch of eager loaded joins |
---|
353 | trans.sa_session.expunge( trans.history ) |
---|
354 | history = trans.sa_session.query( model.History ).options( |
---|
355 | eagerload_all( 'active_datasets.creating_job_associations.job.workflow_invocation_step.workflow_invocation.workflow' ), |
---|
356 | eagerload_all( 'active_datasets.children' ) |
---|
357 | ).get( id ) |
---|
358 | assert history |
---|
359 | assert history.user and ( history.user.id == trans.user.id ) or ( history.id == trans.history.id ) |
---|
360 | # Resolve jobs and workflow invocations for the datasets in the history |
---|
361 | # items is filled with items (hdas, jobs, or workflows) that go at the |
---|
362 | # top level |
---|
363 | items = [] |
---|
364 | # First go through and group hdas by job, if there is no job they get |
---|
365 | # added directly to items |
---|
366 | jobs = odict() |
---|
367 | for hda in history.active_datasets: |
---|
368 | if hda.visible == False: |
---|
369 | continue |
---|
370 | # Follow "copied from ..." association until we get to the original |
---|
371 | # instance of the dataset |
---|
372 | original_hda = hda |
---|
373 | ## while original_hda.copied_from_history_dataset_association: |
---|
374 | ## original_hda = original_hda.copied_from_history_dataset_association |
---|
375 | # Check if the job has a creating job, most should, datasets from |
---|
376 | # before jobs were tracked, or from the upload tool before it |
---|
377 | # created a job, may not |
---|
378 | if not original_hda.creating_job_associations: |
---|
379 | items.append( ( hda, None ) ) |
---|
380 | # Attach hda to correct job |
---|
381 | # -- there should only be one creating_job_association, so this |
---|
382 | # loop body should only be hit once |
---|
383 | for assoc in original_hda.creating_job_associations: |
---|
384 | job = assoc.job |
---|
385 | if job in jobs: |
---|
386 | jobs[ job ].append( ( hda, None ) ) |
---|
387 | else: |
---|
388 | jobs[ job ] = [ ( hda, None ) ] |
---|
389 | # Second, go through the jobs and connect to workflows |
---|
390 | wf_invocations = odict() |
---|
391 | for job, hdas in jobs.iteritems(): |
---|
392 | # Job is attached to a workflow step, follow it to the |
---|
393 | # workflow_invocation and group |
---|
394 | if job.workflow_invocation_step: |
---|
395 | wf_invocation = job.workflow_invocation_step.workflow_invocation |
---|
396 | if wf_invocation in wf_invocations: |
---|
397 | wf_invocations[ wf_invocation ].append( ( job, hdas ) ) |
---|
398 | else: |
---|
399 | wf_invocations[ wf_invocation ] = [ ( job, hdas ) ] |
---|
400 | # Not attached to a workflow, add to items |
---|
401 | else: |
---|
402 | items.append( ( job, hdas ) ) |
---|
403 | # Finally, add workflow invocations to items, which should now |
---|
404 | # contain all hdas with some level of grouping |
---|
405 | items.extend( wf_invocations.items() ) |
---|
406 | # Sort items by age |
---|
407 | items.sort( key=( lambda x: x[0].create_time ), reverse=True ) |
---|
408 | # |
---|
409 | return trans.fill_template( "history/display_structured.mako", items=items ) |
---|
410 | |
---|
411 | @web.expose |
---|
412 | def delete_current( self, trans ): |
---|
413 | """Delete just the active history -- this does not require a logged in user.""" |
---|
414 | history = trans.get_history() |
---|
415 | if history.users_shared_with: |
---|
416 | return trans.show_error_message( "History (%s) has been shared with others, unshare it before deleting it. " % history.name ) |
---|
417 | if not history.deleted: |
---|
418 | history.deleted = True |
---|
419 | trans.sa_session.add( history ) |
---|
420 | trans.sa_session.flush() |
---|
421 | trans.log_event( "History id %d marked as deleted" % history.id ) |
---|
422 | # Regardless of whether it was previously deleted, we make a new history active |
---|
423 | trans.new_history() |
---|
424 | return trans.show_ok_message( "History deleted, a new history is active", refresh_frames=['history'] ) |
---|
425 | |
---|
426 | @web.expose |
---|
427 | @web.require_login( "rate items" ) |
---|
428 | @web.json |
---|
429 | def rate_async( self, trans, id, rating ): |
---|
430 | """ Rate a history asynchronously and return updated community data. """ |
---|
431 | |
---|
432 | history = self.get_history( trans, id, check_ownership=False, check_accessible=True ) |
---|
433 | if not history: |
---|
434 | return trans.show_error_message( "The specified history does not exist." ) |
---|
435 | |
---|
436 | # Rate history. |
---|
437 | history_rating = self.rate_item( trans.sa_session, trans.get_user(), history, rating ) |
---|
438 | |
---|
439 | return self.get_ave_item_rating_data( trans.sa_session, history ) |
---|
440 | |
---|
441 | @web.expose |
---|
442 | def rename_async( self, trans, id=None, new_name=None ): |
---|
443 | history = self.get_history( trans, id ) |
---|
444 | # Check that the history exists, and is either owned by the current |
---|
445 | # user (if logged in) or the current history |
---|
446 | assert history is not None |
---|
447 | if history.user is None: |
---|
448 | assert history == trans.get_history() |
---|
449 | else: |
---|
450 | assert history.user == trans.user |
---|
451 | # Rename |
---|
452 | history.name = new_name |
---|
453 | trans.sa_session.add( history ) |
---|
454 | trans.sa_session.flush() |
---|
455 | return history.name |
---|
456 | |
---|
457 | @web.expose |
---|
458 | @web.require_login( "use Galaxy histories" ) |
---|
459 | def annotate_async( self, trans, id, new_annotation=None, **kwargs ): |
---|
460 | history = self.get_history( trans, id ) |
---|
461 | if new_annotation: |
---|
462 | # Sanitize annotation before adding it. |
---|
463 | new_annotation = sanitize_html( new_annotation, 'utf-8', 'text/html' ) |
---|
464 | self.add_item_annotation( trans.sa_session, trans.get_user(), history, new_annotation ) |
---|
465 | trans.sa_session.flush() |
---|
466 | return new_annotation |
---|
467 | |
---|
468 | def import_archive( self, trans, archived_history=None, gzip=True ): |
---|
469 | """ Import a history. """ |
---|
470 | |
---|
471 | def file_in_dir( file_path, a_dir ): |
---|
472 | """ Returns true if file is in directory. """ |
---|
473 | abs_file_path = os.path.abspath( file_path ) |
---|
474 | return os.path.split( abs_file_path )[0] == a_dir |
---|
475 | |
---|
476 | if archived_history is not None: |
---|
477 | try: |
---|
478 | history_archive_file = tarfile.open( archived_history.file.name ) |
---|
479 | |
---|
480 | # Unpack archive in temporary directory. |
---|
481 | temp_output_dir = tempfile.mkdtemp() |
---|
482 | history_archive_file.extractall( path=temp_output_dir ) |
---|
483 | history_archive_file.close() |
---|
484 | |
---|
485 | # |
---|
486 | # Create history. |
---|
487 | # |
---|
488 | history_attr_file_name = os.path.join( temp_output_dir, 'history_attrs.txt') |
---|
489 | if not file_in_dir( history_attr_file_name, temp_output_dir ): |
---|
490 | raise Exception( "Invalid location for history attributes file: %s" % history_attr_file_name ) |
---|
491 | history_attr_in = open( history_attr_file_name, 'rb' ) |
---|
492 | history_attr_str = '' |
---|
493 | buffsize = 1048576 |
---|
494 | try: |
---|
495 | while True: |
---|
496 | history_attr_str += history_attr_in.read( buffsize ) |
---|
497 | if not history_attr_str or len( history_attr_str ) % buffsize != 0: |
---|
498 | break |
---|
499 | except OverflowError: |
---|
500 | pass |
---|
501 | history_attr_in.close() |
---|
502 | history_attrs = from_json_string( history_attr_str ) |
---|
503 | |
---|
504 | # Create history. |
---|
505 | new_history = model.History( name='imported from archive: %s' % history_attrs['name'].encode( 'utf-8' ), user=trans.user ) |
---|
506 | trans.sa_session.add( new_history ) |
---|
507 | |
---|
508 | new_history.hid_counter = history_attrs['hid_counter'] |
---|
509 | new_history.genome_build = history_attrs['genome_build'] |
---|
510 | trans.sa_session.flush() |
---|
511 | |
---|
512 | # Builds a tag string for a tag, value pair. |
---|
513 | def get_tag_str( tag, value ): |
---|
514 | if not value: |
---|
515 | return tag |
---|
516 | else: |
---|
517 | return tag + ":" + value |
---|
518 | |
---|
519 | # Add annotation, tags. |
---|
520 | if trans.user: |
---|
521 | self.add_item_annotation( trans.sa_session, trans.get_user(), new_history, history_attrs[ 'annotation' ] ) |
---|
522 | for tag, value in history_attrs[ 'tags' ].items(): |
---|
523 | trans.app.tag_handler.apply_item_tags( trans, trans.user, new_history, get_tag_str( tag, value ) ) |
---|
524 | |
---|
525 | # |
---|
526 | # Create datasets. |
---|
527 | # |
---|
528 | datasets_attrs_file_name = os.path.join( temp_output_dir, 'datasets_attrs.txt') |
---|
529 | if not file_in_dir( datasets_attrs_file_name, temp_output_dir ): |
---|
530 | raise Exception( "Invalid location for dataset attributes file: %s" % datasets_attrs_file_name ) |
---|
531 | datasets_attr_in = open( datasets_attrs_file_name, 'rb' ) |
---|
532 | datasets_attr_str = '' |
---|
533 | buffsize = 1048576 |
---|
534 | try: |
---|
535 | while True: |
---|
536 | datasets_attr_str += datasets_attr_in.read( buffsize ) |
---|
537 | if not datasets_attr_str or len( datasets_attr_str ) % buffsize != 0: |
---|
538 | break |
---|
539 | except OverflowError: |
---|
540 | pass |
---|
541 | datasets_attr_in.close() |
---|
542 | datasets_attrs = from_json_string( datasets_attr_str ) |
---|
543 | |
---|
544 | # Create datasets. |
---|
545 | for dataset_attrs in datasets_attrs: |
---|
546 | metadata = dataset_attrs['metadata'] |
---|
547 | |
---|
548 | # Create dataset and HDA. |
---|
549 | hda = model.HistoryDatasetAssociation( name = dataset_attrs['name'].encode( 'utf-8' ), |
---|
550 | extension = dataset_attrs['extension'], |
---|
551 | info = dataset_attrs['info'].encode( 'utf-8' ), |
---|
552 | blurb = dataset_attrs['blurb'], |
---|
553 | peek = dataset_attrs['peek'], |
---|
554 | designation = dataset_attrs['designation'], |
---|
555 | visible = dataset_attrs['visible'], |
---|
556 | dbkey = metadata['dbkey'], |
---|
557 | metadata = metadata, |
---|
558 | history = new_history, |
---|
559 | create_dataset = True, |
---|
560 | sa_session = trans.sa_session ) |
---|
561 | hda.state = hda.states.OK |
---|
562 | trans.sa_session.add( hda ) |
---|
563 | trans.sa_session.flush() |
---|
564 | new_history.add_dataset( hda, genome_build = None ) |
---|
565 | hda.hid = dataset_attrs['hid'] # Overwrite default hid set when HDA added to history. |
---|
566 | permissions = trans.app.security_agent.history_get_default_permissions( new_history ) |
---|
567 | trans.app.security_agent.set_all_dataset_permissions( hda.dataset, permissions ) |
---|
568 | trans.sa_session.flush() |
---|
569 | |
---|
570 | # Do security check and copy dataset data. |
---|
571 | temp_dataset_file_name = os.path.join( temp_output_dir, dataset_attrs['file_name'] ) |
---|
572 | if not file_in_dir( temp_dataset_file_name, os.path.join( temp_output_dir, "datasets" ) ): |
---|
573 | raise Exception( "Invalid dataset path: %s" % temp_dataset_file_name ) |
---|
574 | shutil.move( temp_dataset_file_name, hda.file_name ) |
---|
575 | |
---|
576 | # Set tags, annotations. |
---|
577 | if trans.user: |
---|
578 | self.add_item_annotation( trans.sa_session, trans.get_user(), hda, dataset_attrs[ 'annotation' ] ) |
---|
579 | for tag, value in dataset_attrs[ 'tags' ].items(): |
---|
580 | trans.app.tag_handler.apply_item_tags( trans, trans.user, hda, get_tag_str( tag, value ) ) |
---|
581 | trans.sa_session.flush() |
---|
582 | |
---|
583 | # |
---|
584 | # Create jobs. |
---|
585 | # |
---|
586 | |
---|
587 | # Read jobs attributes. |
---|
588 | jobs_attr_file_name = os.path.join( temp_output_dir, 'jobs_attrs.txt') |
---|
589 | if not file_in_dir( jobs_attr_file_name, temp_output_dir ): |
---|
590 | raise Exception( "Invalid location for jobs' attributes file: %s" % jobs_attr_file_name ) |
---|
591 | jobs_attr_in = open( jobs_attr_file_name, 'rb' ) |
---|
592 | jobs_attr_str = '' |
---|
593 | buffsize = 1048576 |
---|
594 | try: |
---|
595 | while True: |
---|
596 | jobs_attr_str += jobs_attr_in.read( buffsize ) |
---|
597 | if not jobs_attr_str or len( jobs_attr_str ) % buffsize != 0: |
---|
598 | break |
---|
599 | except OverflowError: |
---|
600 | pass |
---|
601 | jobs_attr_in.close() |
---|
602 | |
---|
603 | # Decode jobs attributes. |
---|
604 | def as_hda( obj_dct ): |
---|
605 | """ Hook to 'decode' an HDA; method uses history and HID to get the HDA represented by |
---|
606 | the encoded object. This only works because HDAs are created above. """ |
---|
607 | if obj_dct.get( '__HistoryDatasetAssociation__', False ): |
---|
608 | return trans.sa_session.query( model.HistoryDatasetAssociation ) \ |
---|
609 | .filter_by( history=new_history, hid=obj_dct['hid'] ).first() |
---|
610 | return obj_dct |
---|
611 | jobs_attrs = from_json_string( jobs_attr_str, object_hook=as_hda ) |
---|
612 | |
---|
613 | # Create each job. |
---|
614 | for job_attrs in jobs_attrs: |
---|
615 | imported_job = model.Job() |
---|
616 | imported_job.user = trans.user |
---|
617 | imported_job.session = trans.get_galaxy_session().id |
---|
618 | imported_job.history = new_history |
---|
619 | imported_job.tool_id = job_attrs[ 'tool_id' ] |
---|
620 | imported_job.tool_version = job_attrs[ 'tool_version' ] |
---|
621 | imported_job.set_state( job_attrs[ 'state' ] ) |
---|
622 | imported_job.imported = True |
---|
623 | trans.sa_session.add( imported_job ) |
---|
624 | trans.sa_session.flush() |
---|
625 | |
---|
626 | class HistoryDatasetAssociationIDEncoder( simplejson.JSONEncoder ): |
---|
627 | """ Custom JSONEncoder for a HistoryDatasetAssociation that encodes an HDA as its ID. """ |
---|
628 | def default( self, obj ): |
---|
629 | """ Encode an HDA, default encoding for everything else. """ |
---|
630 | if isinstance( obj, model.HistoryDatasetAssociation ): |
---|
631 | return obj.id |
---|
632 | return simplejson.JSONEncoder.default( self, obj ) |
---|
633 | |
---|
634 | # Set parameters. May be useful to look at metadata.py for creating parameters. |
---|
635 | # TODO: there may be a better way to set parameters, e.g.: |
---|
636 | # for name, value in tool.params_to_strings( incoming, trans.app ).iteritems(): |
---|
637 | # job.add_parameter( name, value ) |
---|
638 | # to make this work, we'd need to flesh out the HDA objects. The code below is |
---|
639 | # relatively similar. |
---|
640 | for name, value in job_attrs[ 'params' ].items(): |
---|
641 | # Transform parameter values when necessary. |
---|
642 | if isinstance( value, model.HistoryDatasetAssociation ): |
---|
643 | # HDA input: use hid to find input. |
---|
644 | input_hda = trans.sa_session.query( model.HistoryDatasetAssociation ) \ |
---|
645 | .filter_by( history=new_history, hid=value.hid ).first() |
---|
646 | value = input_hda.id |
---|
647 | #print "added parameter %s-->%s to job %i" % ( name, value, imported_job.id ) |
---|
648 | imported_job.add_parameter( name, to_json_string( value, cls=HistoryDatasetAssociationIDEncoder ) ) |
---|
649 | |
---|
650 | # TODO: Connect jobs to input datasets. |
---|
651 | |
---|
652 | # Connect jobs to output datasets. |
---|
653 | for output_hid in job_attrs[ 'output_datasets' ]: |
---|
654 | #print "%s job has output dataset %i" % (imported_job.id, output_hid) |
---|
655 | output_hda = trans.sa_session.query( model.HistoryDatasetAssociation ) \ |
---|
656 | .filter_by( history=new_history, hid=output_hid ).first() |
---|
657 | if output_hda: |
---|
658 | imported_job.add_output_dataset( output_hda.name, output_hda ) |
---|
659 | trans.sa_session.flush() |
---|
660 | |
---|
661 | # Cleanup. |
---|
662 | if os.path.exists( temp_output_dir ): |
---|
663 | shutil.rmtree( temp_output_dir ) |
---|
664 | |
---|
665 | return trans.show_ok_message( message="History '%s' has been imported. " % history_attrs['name'] ) |
---|
666 | except Exception, e: |
---|
667 | return trans.show_error_message( 'Error importing history archive. ' + str( e ) ) |
---|
668 | |
---|
669 | return trans.show_form( |
---|
670 | web.FormBuilder( web.url_for(), "Import a History from an Archive", submit_text="Submit" ) |
---|
671 | .add_input( "file", "Archived History File", "archived_history", value=None, error=None ) |
---|
672 | ) |
---|
673 | |
---|
674 | @web.expose |
---|
675 | def export_archive( self, trans, id=None, gzip=True, include_hidden=False, include_deleted=False ): |
---|
676 | """ Export a history to an archive. """ |
---|
677 | |
---|
678 | # |
---|
679 | # Convert options to booleans. |
---|
680 | # |
---|
681 | if isinstance( gzip, basestring ): |
---|
682 | gzip = ( gzip in [ 'True', 'true', 'T', 't' ] ) |
---|
683 | if isinstance( include_hidden, basestring ): |
---|
684 | include_hidden = ( include_hidden in [ 'True', 'true', 'T', 't' ] ) |
---|
685 | if isinstance( include_deleted, basestring ): |
---|
686 | include_deleted = ( include_deleted in [ 'True', 'true', 'T', 't' ] ) |
---|
687 | |
---|
688 | # |
---|
689 | # Get history to export. |
---|
690 | # |
---|
691 | if id: |
---|
692 | history = self.get_history( trans, id, check_ownership=False, check_accessible=True ) |
---|
693 | else: |
---|
694 | # Use current history. |
---|
695 | history = trans.history |
---|
696 | id = trans.security.encode_id( history.id ) |
---|
697 | |
---|
698 | if not history: |
---|
699 | return trans.show_error_message( "This history does not exist or you cannot export this history." ) |
---|
700 | |
---|
701 | # |
---|
702 | # If history has already been exported and it has not changed since export, stream it. |
---|
703 | # |
---|
704 | jeha = trans.sa_session.query( model.JobExportHistoryArchive ).filter_by( history=history ) \ |
---|
705 | .order_by( model.JobExportHistoryArchive.id.desc() ).first() |
---|
706 | if jeha and ( jeha.job.state not in [ model.Job.states.ERROR, model.Job.states.DELETED ] ) \ |
---|
707 | and jeha.job.update_time > history.update_time: |
---|
708 | if jeha.job.state == model.Job.states.OK: |
---|
709 | # Stream archive. |
---|
710 | valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' |
---|
711 | hname = history.name |
---|
712 | hname = ''.join(c in valid_chars and c or '_' for c in hname)[0:150] |
---|
713 | trans.response.headers["Content-Disposition"] = "attachment; filename=Galaxy-History-%s.tar" % ( hname ) |
---|
714 | if jeha.compressed: |
---|
715 | trans.response.headers["Content-Disposition"] += ".gz" |
---|
716 | trans.response.set_content_type( 'application/x-gzip' ) |
---|
717 | else: |
---|
718 | trans.response.set_content_type( 'application/x-tar' ) |
---|
719 | return open( jeha.dataset.file_name ) |
---|
720 | elif jeha.job.state in [ model.Job.states.RUNNING, model.Job.states.QUEUED, model.Job.states.WAITING ]: |
---|
721 | return trans.show_message( "Still exporting history %(n)s; please check back soon. Link: <a href='%(s)s'>%(s)s</a>" \ |
---|
722 | % ( { 'n' : history.name, 's' : url_for( action="export_archive", id=id, qualified=True ) } ) ) |
---|
723 | |
---|
724 | # Run job to do export. |
---|
725 | history_exp_tool = trans.app.toolbox.tools_by_id[ '__EXPORT_HISTORY__' ] |
---|
726 | params = { |
---|
727 | 'history_to_export' : history, |
---|
728 | 'compress' : gzip, |
---|
729 | 'include_hidden' : include_hidden, |
---|
730 | 'include_deleted' : include_deleted } |
---|
731 | history_exp_tool.execute( trans, incoming = params, set_output_hid = True ) |
---|
732 | return trans.show_message( "Exporting History '%(n)s'. Use this link to download \ |
---|
733 | the archive or import it to another Galaxy server: \ |
---|
734 | <a href='%(u)s'>%(u)s</a>" \ |
---|
735 | % ( { 'n' : history.name, 'u' : url_for( action="export_archive", id=id, qualified=True ) } ) ) |
---|
736 | |
---|
737 | @web.expose |
---|
738 | @web.json |
---|
739 | @web.require_login( "get history name and link" ) |
---|
740 | def get_name_and_link_async( self, trans, id=None ): |
---|
741 | """ Returns history's name and link. """ |
---|
742 | history = self.get_history( trans, id, False ) |
---|
743 | |
---|
744 | if self.create_item_slug( trans.sa_session, history ): |
---|
745 | trans.sa_session.flush() |
---|
746 | return_dict = { |
---|
747 | "name" : history.name, |
---|
748 | "link" : url_for( action="display_by_username_and_slug", username=history.user.username, slug=history.slug ) } |
---|
749 | return return_dict |
---|
750 | |
---|
751 | @web.expose |
---|
752 | @web.require_login( "set history's accessible flag" ) |
---|
753 | def set_accessible_async( self, trans, id=None, accessible=False ): |
---|
754 | """ Set history's importable attribute and slug. """ |
---|
755 | history = self.get_history( trans, id, True ) |
---|
756 | |
---|
757 | # Only set if importable value would change; this prevents a change in the update_time unless attribute really changed. |
---|
758 | importable = accessible in ['True', 'true', 't', 'T']; |
---|
759 | if history and history.importable != importable: |
---|
760 | if importable: |
---|
761 | self._make_item_accessible( trans.sa_session, history ) |
---|
762 | else: |
---|
763 | history.importable = importable |
---|
764 | trans.sa_session.flush() |
---|
765 | |
---|
766 | return |
---|
767 | |
---|
768 | @web.expose |
---|
769 | @web.require_login( "modify Galaxy items" ) |
---|
770 | def set_slug_async( self, trans, id, new_slug ): |
---|
771 | history = self.get_history( trans, id ) |
---|
772 | if history: |
---|
773 | history.slug = new_slug |
---|
774 | trans.sa_session.flush() |
---|
775 | return history.slug |
---|
776 | |
---|
777 | @web.expose |
---|
778 | def get_item_content_async( self, trans, id ): |
---|
779 | """ Returns item content in HTML format. """ |
---|
780 | |
---|
781 | history = self.get_history( trans, id, False, True ) |
---|
782 | if history is None: |
---|
783 | raise web.httpexceptions.HTTPNotFound() |
---|
784 | |
---|
785 | # Get datasets. |
---|
786 | datasets = self.get_history_datasets( trans, history ) |
---|
787 | # Get annotations. |
---|
788 | history.annotation = self.get_item_annotation_str( trans.sa_session, history.user, history ) |
---|
789 | for dataset in datasets: |
---|
790 | dataset.annotation = self.get_item_annotation_str( trans.sa_session, history.user, dataset ) |
---|
791 | return trans.stream_template_mako( "/history/item_content.mako", item = history, item_data = datasets ) |
---|
792 | |
---|
793 | @web.expose |
---|
794 | def name_autocomplete_data( self, trans, q=None, limit=None, timestamp=None ): |
---|
795 | """Return autocomplete data for history names""" |
---|
796 | user = trans.get_user() |
---|
797 | if not user: |
---|
798 | return |
---|
799 | |
---|
800 | ac_data = "" |
---|
801 | for history in trans.sa_session.query( model.History ).filter_by( user=user ).filter( func.lower( model.History.name ) .like(q.lower() + "%") ): |
---|
802 | ac_data = ac_data + history.name + "\n" |
---|
803 | return ac_data |
---|
804 | |
---|
805 | @web.expose |
---|
806 | def imp( self, trans, id=None, confirm=False, **kwd ): |
---|
807 | """Import another user's history via a shared URL""" |
---|
808 | msg = "" |
---|
809 | user = trans.get_user() |
---|
810 | user_history = trans.get_history() |
---|
811 | # Set referer message |
---|
812 | if 'referer' in kwd: |
---|
813 | referer = kwd['referer'] |
---|
814 | else: |
---|
815 | referer = trans.request.referer |
---|
816 | if referer is not "": |
---|
817 | referer_message = "<a href='%s'>return to the previous page</a>" % referer |
---|
818 | else: |
---|
819 | referer_message = "<a href='%s'>go to Galaxy's start page</a>" % url_for( '/' ) |
---|
820 | |
---|
821 | # Do import. |
---|
822 | if not id: |
---|
823 | return trans.show_error_message( "You must specify a history you want to import.<br>You can %s." % referer_message, use_panels=True ) |
---|
824 | import_history = self.get_history( trans, id, check_ownership=False, check_accessible=False ) |
---|
825 | if not import_history: |
---|
826 | return trans.show_error_message( "The specified history does not exist.<br>You can %s." % referer_message, use_panels=True ) |
---|
827 | # History is importable if user is admin or it's accessible. TODO: probably want to have app setting to enable admin access to histories. |
---|
828 | if not trans.user_is_admin() and not self.security_check( user, import_history, check_ownership=False, check_accessible=True ): |
---|
829 | return trans.show_error_message( "You cannot access this history.<br>You can %s." % referer_message, use_panels=True ) |
---|
830 | if user: |
---|
831 | if import_history.user_id == user.id: |
---|
832 | return trans.show_error_message( "You cannot import your own history.<br>You can %s." % referer_message, use_panels=True ) |
---|
833 | new_history = import_history.copy( target_user=user ) |
---|
834 | new_history.name = "imported: " + new_history.name |
---|
835 | new_history.user_id = user.id |
---|
836 | galaxy_session = trans.get_galaxy_session() |
---|
837 | try: |
---|
838 | association = trans.sa_session.query( trans.app.model.GalaxySessionToHistoryAssociation ) \ |
---|
839 | .filter_by( session_id=galaxy_session.id, history_id=new_history.id ) \ |
---|
840 | .first() |
---|
841 | except: |
---|
842 | association = None |
---|
843 | new_history.add_galaxy_session( galaxy_session, association=association ) |
---|
844 | trans.sa_session.add( new_history ) |
---|
845 | trans.sa_session.flush() |
---|
846 | # Set imported history to be user's current history. |
---|
847 | trans.set_history( new_history ) |
---|
848 | return trans.show_ok_message( |
---|
849 | message="""History "%s" has been imported. <br>You can <a href="%s">start using this history</a> or %s.""" |
---|
850 | % ( new_history.name, web.url_for( '/' ), referer_message ), use_panels=True ) |
---|
851 | elif not user_history or not user_history.datasets or confirm: |
---|
852 | new_history = import_history.copy() |
---|
853 | new_history.name = "imported: " + new_history.name |
---|
854 | new_history.user_id = None |
---|
855 | galaxy_session = trans.get_galaxy_session() |
---|
856 | try: |
---|
857 | association = trans.sa_session.query( trans.app.model.GalaxySessionToHistoryAssociation ) \ |
---|
858 | .filter_by( session_id=galaxy_session.id, history_id=new_history.id ) \ |
---|
859 | .first() |
---|
860 | except: |
---|
861 | association = None |
---|
862 | new_history.add_galaxy_session( galaxy_session, association=association ) |
---|
863 | trans.sa_session.add( new_history ) |
---|
864 | trans.sa_session.flush() |
---|
865 | trans.set_history( new_history ) |
---|
866 | return trans.show_ok_message( |
---|
867 | message="""History "%s" has been imported. <br>You can <a href="%s">start using this history</a> or %s.""" |
---|
868 | % ( new_history.name, web.url_for( '/' ), referer_message ), use_panels=True ) |
---|
869 | return trans.show_warn_message( """ |
---|
870 | Warning! If you import this history, you will lose your current |
---|
871 | history. <br>You can <a href="%s">continue and import this history</a> or %s. |
---|
872 | """ % ( web.url_for( id=id, confirm=True, referer=trans.request.referer ), referer_message ), use_panels=True ) |
---|
873 | |
---|
874 | @web.expose |
---|
875 | def view( self, trans, id=None ): |
---|
876 | """View a history. If a history is importable, then it is viewable by any user.""" |
---|
877 | # Get history to view. |
---|
878 | if not id: |
---|
879 | return trans.show_error_message( "You must specify a history you want to view." ) |
---|
880 | history_to_view = self.get_history( trans, id, False) |
---|
881 | # Integrity checks. |
---|
882 | if not history_to_view: |
---|
883 | return trans.show_error_message( "The specified history does not exist." ) |
---|
884 | # Admin users can view any history |
---|
885 | if not trans.user_is_admin() and not history_to_view.importable: |
---|
886 | error( "Either you are not allowed to view this history or the owner of this history has not made it accessible." ) |
---|
887 | # View history. |
---|
888 | datasets = self.get_history_datasets( trans, history_to_view ) |
---|
889 | return trans.stream_template_mako( "history/view.mako", |
---|
890 | history = history_to_view, |
---|
891 | datasets = datasets, |
---|
892 | show_deleted = False ) |
---|
893 | |
---|
894 | @web.expose |
---|
895 | def display_by_username_and_slug( self, trans, username, slug ): |
---|
896 | """ Display history based on a username and slug. """ |
---|
897 | |
---|
898 | # Get history. |
---|
899 | session = trans.sa_session |
---|
900 | user = session.query( model.User ).filter_by( username=username ).first() |
---|
901 | history = trans.sa_session.query( model.History ).filter_by( user=user, slug=slug, deleted=False ).first() |
---|
902 | if history is None: |
---|
903 | raise web.httpexceptions.HTTPNotFound() |
---|
904 | # Security check raises error if user cannot access history. |
---|
905 | self.security_check( trans.get_user(), history, False, True) |
---|
906 | |
---|
907 | # Get datasets. |
---|
908 | datasets = self.get_history_datasets( trans, history ) |
---|
909 | # Get annotations. |
---|
910 | history.annotation = self.get_item_annotation_str( trans.sa_session, history.user, history ) |
---|
911 | for dataset in datasets: |
---|
912 | dataset.annotation = self.get_item_annotation_str( trans.sa_session, history.user, dataset ) |
---|
913 | |
---|
914 | # Get rating data. |
---|
915 | user_item_rating = 0 |
---|
916 | if trans.get_user(): |
---|
917 | user_item_rating = self.get_user_item_rating( trans.sa_session, trans.get_user(), history ) |
---|
918 | if user_item_rating: |
---|
919 | user_item_rating = user_item_rating.rating |
---|
920 | else: |
---|
921 | user_item_rating = 0 |
---|
922 | ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, history ) |
---|
923 | return trans.stream_template_mako( "history/display.mako", item = history, item_data = datasets, |
---|
924 | user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings ) |
---|
925 | |
---|
926 | @web.expose |
---|
927 | @web.require_login( "share Galaxy histories" ) |
---|
928 | def sharing( self, trans, id=None, histories=[], **kwargs ): |
---|
929 | """ Handle history sharing. """ |
---|
930 | |
---|
931 | # Get session and histories. |
---|
932 | session = trans.sa_session |
---|
933 | # Id values take precedence over histories passed in; last resort is current history. |
---|
934 | if id: |
---|
935 | ids = util.listify( id ) |
---|
936 | if ids: |
---|
937 | histories = [ self.get_history( trans, history_id ) for history_id in ids ] |
---|
938 | elif not histories: |
---|
939 | histories = [ trans.history ] |
---|
940 | |
---|
941 | # Do operation on histories. |
---|
942 | for history in histories: |
---|
943 | if 'make_accessible_via_link' in kwargs: |
---|
944 | self._make_item_accessible( trans.sa_session, history ) |
---|
945 | elif 'make_accessible_and_publish' in kwargs: |
---|
946 | self._make_item_accessible( trans.sa_session, history ) |
---|
947 | history.published = True |
---|
948 | elif 'publish' in kwargs: |
---|
949 | if history.importable: |
---|
950 | history.published = True |
---|
951 | else: |
---|
952 | # TODO: report error here. |
---|
953 | pass |
---|
954 | elif 'disable_link_access' in kwargs: |
---|
955 | history.importable = False |
---|
956 | elif 'unpublish' in kwargs: |
---|
957 | history.published = False |
---|
958 | elif 'disable_link_access_and_unpublish' in kwargs: |
---|
959 | history.importable = history.published = False |
---|
960 | elif 'unshare_user' in kwargs: |
---|
961 | user = trans.sa_session.query( trans.app.model.User ).get( trans.security.decode_id( kwargs[ 'unshare_user' ] ) ) |
---|
962 | # Look for and delete sharing relation for history-user. |
---|
963 | deleted_sharing_relation = False |
---|
964 | husas = trans.sa_session.query( trans.app.model.HistoryUserShareAssociation ).filter_by( user=user, history=history ).all() |
---|
965 | if husas: |
---|
966 | deleted_sharing_relation = True |
---|
967 | for husa in husas: |
---|
968 | trans.sa_session.delete( husa ) |
---|
969 | if not deleted_sharing_relation: |
---|
970 | message = "History '%s' does not seem to be shared with user '%s'" % ( history.name, user.email ) |
---|
971 | return trans.fill_template( '/sharing_base.mako', item=history, |
---|
972 | message=message, status='error' ) |
---|
973 | |
---|
974 | |
---|
975 | # Legacy issue: histories made accessible before recent updates may not have a slug. Create slug for any histories that need them. |
---|
976 | for history in histories: |
---|
977 | if history.importable and not history.slug: |
---|
978 | self._make_item_accessible( trans.sa_session, history ) |
---|
979 | |
---|
980 | session.flush() |
---|
981 | |
---|
982 | return trans.fill_template( "/sharing_base.mako", item=history ) |
---|
983 | |
---|
984 | @web.expose |
---|
985 | @web.require_login( "share histories with other users" ) |
---|
986 | def share( self, trans, id=None, email="", **kwd ): |
---|
987 | # If a history contains both datasets that can be shared and others that cannot be shared with the desired user, |
---|
988 | # then the entire history is shared, and the protected datasets will be visible, but inaccessible ( greyed out ) |
---|
989 | # in the cloned history |
---|
990 | params = util.Params( kwd ) |
---|
991 | user = trans.get_user() |
---|
992 | # TODO: we have too many error messages floating around in here - we need |
---|
993 | # to incorporate the messaging system used by the libraries that will display |
---|
994 | # a message on any page. |
---|
995 | err_msg = util.restore_text( params.get( 'err_msg', '' ) ) |
---|
996 | if not email: |
---|
997 | if not id: |
---|
998 | # Default to the current history |
---|
999 | id = trans.security.encode_id( trans.history.id ) |
---|
1000 | id = util.listify( id ) |
---|
1001 | send_to_err = err_msg |
---|
1002 | histories = [] |
---|
1003 | for history_id in id: |
---|
1004 | histories.append( self.get_history( trans, history_id ) ) |
---|
1005 | return trans.fill_template( "/history/share.mako", |
---|
1006 | histories=histories, |
---|
1007 | email=email, |
---|
1008 | send_to_err=send_to_err ) |
---|
1009 | histories, send_to_users, send_to_err = self._get_histories_and_users( trans, user, id, email ) |
---|
1010 | if not send_to_users: |
---|
1011 | if not send_to_err: |
---|
1012 | send_to_err += "%s is not a valid Galaxy user. %s" % ( email, err_msg ) |
---|
1013 | return trans.fill_template( "/history/share.mako", |
---|
1014 | histories=histories, |
---|
1015 | email=email, |
---|
1016 | send_to_err=send_to_err ) |
---|
1017 | if params.get( 'share_button', False ): |
---|
1018 | # The user has not yet made a choice about how to share, so dictionaries will be built for display |
---|
1019 | can_change, cannot_change, no_change_needed, unique_no_change_needed, send_to_err = \ |
---|
1020 | self._populate_restricted( trans, user, histories, send_to_users, None, send_to_err, unique=True ) |
---|
1021 | send_to_err += err_msg |
---|
1022 | if cannot_change and not no_change_needed and not can_change: |
---|
1023 | send_to_err = "The histories you are sharing do not contain any datasets that can be accessed by the users with which you are sharing." |
---|
1024 | return trans.fill_template( "/history/share.mako", histories=histories, email=email, send_to_err=send_to_err ) |
---|
1025 | if can_change or cannot_change: |
---|
1026 | return trans.fill_template( "/history/share.mako", |
---|
1027 | histories=histories, |
---|
1028 | email=email, |
---|
1029 | send_to_err=send_to_err, |
---|
1030 | can_change=can_change, |
---|
1031 | cannot_change=cannot_change, |
---|
1032 | no_change_needed=unique_no_change_needed ) |
---|
1033 | if no_change_needed: |
---|
1034 | return self._share_histories( trans, user, send_to_err, histories=no_change_needed ) |
---|
1035 | elif not send_to_err: |
---|
1036 | # User seems to be sharing an empty history |
---|
1037 | send_to_err = "You cannot share an empty history. " |
---|
1038 | return trans.fill_template( "/history/share.mako", histories=histories, email=email, send_to_err=send_to_err ) |
---|
1039 | |
---|
1040 | @web.expose |
---|
1041 | @web.require_login( "share restricted histories with other users" ) |
---|
1042 | def share_restricted( self, trans, id=None, email="", **kwd ): |
---|
1043 | if 'action' in kwd: |
---|
1044 | action = kwd[ 'action' ] |
---|
1045 | else: |
---|
1046 | err_msg = "Select an action. " |
---|
1047 | return trans.response.send_redirect( url_for( controller='history', |
---|
1048 | action='share', |
---|
1049 | id=id, |
---|
1050 | email=email, |
---|
1051 | err_msg=err_msg, |
---|
1052 | share_button=True ) ) |
---|
1053 | user = trans.get_user() |
---|
1054 | user_roles = user.all_roles() |
---|
1055 | histories, send_to_users, send_to_err = self._get_histories_and_users( trans, user, id, email ) |
---|
1056 | send_to_err = '' |
---|
1057 | # The user has made a choice, so dictionaries will be built for sharing |
---|
1058 | can_change, cannot_change, no_change_needed, unique_no_change_needed, send_to_err = \ |
---|
1059 | self._populate_restricted( trans, user, histories, send_to_users, action, send_to_err ) |
---|
1060 | # Now that we've populated the can_change, cannot_change, and no_change_needed dictionaries, |
---|
1061 | # we'll populate the histories_for_sharing dictionary from each of them. |
---|
1062 | histories_for_sharing = {} |
---|
1063 | if no_change_needed: |
---|
1064 | # Don't need to change anything in cannot_change, so populate as is |
---|
1065 | histories_for_sharing, send_to_err = \ |
---|
1066 | self._populate( trans, histories_for_sharing, no_change_needed, send_to_err ) |
---|
1067 | if cannot_change: |
---|
1068 | # Can't change anything in cannot_change, so populate as is |
---|
1069 | histories_for_sharing, send_to_err = \ |
---|
1070 | self._populate( trans, histories_for_sharing, cannot_change, send_to_err ) |
---|
1071 | # The action here is either 'public' or 'private', so we'll continue to populate the |
---|
1072 | # histories_for_sharing dictionary from the can_change dictionary. |
---|
1073 | for send_to_user, history_dict in can_change.items(): |
---|
1074 | for history in history_dict: |
---|
1075 | # Make sure the current history has not already been shared with the current send_to_user |
---|
1076 | if trans.sa_session.query( trans.app.model.HistoryUserShareAssociation ) \ |
---|
1077 | .filter( and_( trans.app.model.HistoryUserShareAssociation.table.c.user_id == send_to_user.id, |
---|
1078 | trans.app.model.HistoryUserShareAssociation.table.c.history_id == history.id ) ) \ |
---|
1079 | .count() > 0: |
---|
1080 | send_to_err += "History (%s) already shared with user (%s)" % ( history.name, send_to_user.email ) |
---|
1081 | else: |
---|
1082 | # Only deal with datasets that have not been purged |
---|
1083 | for hda in history.activatable_datasets: |
---|
1084 | # If the current dataset is not public, we may need to perform an action on it to |
---|
1085 | # make it accessible by the other user. |
---|
1086 | if not trans.app.security_agent.can_access_dataset( send_to_user.all_roles(), hda.dataset ): |
---|
1087 | # The user with which we are sharing the history does not have access permission on the current dataset |
---|
1088 | if trans.app.security_agent.can_manage_dataset( user_roles, hda.dataset ) and not hda.dataset.library_associations: |
---|
1089 | # The current user has authority to change permissions on the current dataset because |
---|
1090 | # they have permission to manage permissions on the dataset and the dataset is not associated |
---|
1091 | # with a library. |
---|
1092 | if action == "private": |
---|
1093 | trans.app.security_agent.privately_share_dataset( hda.dataset, users=[ user, send_to_user ] ) |
---|
1094 | elif action == "public": |
---|
1095 | trans.app.security_agent.make_dataset_public( hda.dataset ) |
---|
1096 | # Populate histories_for_sharing with the history after performing any requested actions on |
---|
1097 | # it's datasets to make them accessible by the other user. |
---|
1098 | if send_to_user not in histories_for_sharing: |
---|
1099 | histories_for_sharing[ send_to_user ] = [ history ] |
---|
1100 | elif history not in histories_for_sharing[ send_to_user ]: |
---|
1101 | histories_for_sharing[ send_to_user ].append( history ) |
---|
1102 | return self._share_histories( trans, user, send_to_err, histories=histories_for_sharing ) |
---|
1103 | def _get_histories_and_users( self, trans, user, id, email ): |
---|
1104 | if not id: |
---|
1105 | # Default to the current history |
---|
1106 | id = trans.security.encode_id( trans.history.id ) |
---|
1107 | id = util.listify( id ) |
---|
1108 | send_to_err = "" |
---|
1109 | histories = [] |
---|
1110 | for history_id in id: |
---|
1111 | histories.append( self.get_history( trans, history_id ) ) |
---|
1112 | send_to_users = [] |
---|
1113 | for email_address in util.listify( email ): |
---|
1114 | email_address = email_address.strip() |
---|
1115 | if email_address: |
---|
1116 | if email_address == user.email: |
---|
1117 | send_to_err += "You cannot send histories to yourself. " |
---|
1118 | else: |
---|
1119 | send_to_user = trans.sa_session.query( trans.app.model.User ) \ |
---|
1120 | .filter( and_( trans.app.model.User.table.c.email==email_address, |
---|
1121 | trans.app.model.User.table.c.deleted==False ) ) \ |
---|
1122 | .first() |
---|
1123 | if send_to_user: |
---|
1124 | send_to_users.append( send_to_user ) |
---|
1125 | else: |
---|
1126 | send_to_err += "%s is not a valid Galaxy user. " % email_address |
---|
1127 | return histories, send_to_users, send_to_err |
---|
1128 | def _populate( self, trans, histories_for_sharing, other, send_to_err ): |
---|
1129 | # This method will populate the histories_for_sharing dictionary with the users and |
---|
1130 | # histories in other, eliminating histories that have already been shared with the |
---|
1131 | # associated user. No security checking on datasets is performed. |
---|
1132 | # If not empty, the histories_for_sharing dictionary looks like: |
---|
1133 | # { userA: [ historyX, historyY ], userB: [ historyY ] } |
---|
1134 | # other looks like: |
---|
1135 | # { userA: {historyX : [hda, hda], historyY : [hda]}, userB: {historyY : [hda]} } |
---|
1136 | for send_to_user, history_dict in other.items(): |
---|
1137 | for history in history_dict: |
---|
1138 | # Make sure the current history has not already been shared with the current send_to_user |
---|
1139 | if trans.sa_session.query( trans.app.model.HistoryUserShareAssociation ) \ |
---|
1140 | .filter( and_( trans.app.model.HistoryUserShareAssociation.table.c.user_id == send_to_user.id, |
---|
1141 | trans.app.model.HistoryUserShareAssociation.table.c.history_id == history.id ) ) \ |
---|
1142 | .count() > 0: |
---|
1143 | send_to_err += "History (%s) already shared with user (%s)" % ( history.name, send_to_user.email ) |
---|
1144 | else: |
---|
1145 | # Build the dict that will be used for sharing |
---|
1146 | if send_to_user not in histories_for_sharing: |
---|
1147 | histories_for_sharing[ send_to_user ] = [ history ] |
---|
1148 | elif history not in histories_for_sharing[ send_to_user ]: |
---|
1149 | histories_for_sharing[ send_to_user ].append( history ) |
---|
1150 | return histories_for_sharing, send_to_err |
---|
1151 | def _populate_restricted( self, trans, user, histories, send_to_users, action, send_to_err, unique=False ): |
---|
1152 | # The user may be attempting to share histories whose datasets cannot all be accessed by other users. |
---|
1153 | # If this is the case, the user sharing the histories can: |
---|
1154 | # 1) action=='public': choose to make the datasets public if he is permitted to do so |
---|
1155 | # 2) action=='private': automatically create a new "sharing role" allowing protected |
---|
1156 | # datasets to be accessed only by the desired users |
---|
1157 | # This method will populate the can_change, cannot_change and no_change_needed dictionaries, which |
---|
1158 | # are used for either displaying to the user, letting them make 1 of the choices above, or sharing |
---|
1159 | # after the user has made a choice. They will be used for display if 'unique' is True, and will look |
---|
1160 | # like: {historyX : [hda, hda], historyY : [hda] } |
---|
1161 | # For sharing, they will look like: |
---|
1162 | # { userA: {historyX : [hda, hda], historyY : [hda]}, userB: {historyY : [hda]} } |
---|
1163 | can_change = {} |
---|
1164 | cannot_change = {} |
---|
1165 | no_change_needed = {} |
---|
1166 | unique_no_change_needed = {} |
---|
1167 | user_roles = user.all_roles() |
---|
1168 | for history in histories: |
---|
1169 | for send_to_user in send_to_users: |
---|
1170 | # Make sure the current history has not already been shared with the current send_to_user |
---|
1171 | if trans.sa_session.query( trans.app.model.HistoryUserShareAssociation ) \ |
---|
1172 | .filter( and_( trans.app.model.HistoryUserShareAssociation.table.c.user_id == send_to_user.id, |
---|
1173 | trans.app.model.HistoryUserShareAssociation.table.c.history_id == history.id ) ) \ |
---|
1174 | .count() > 0: |
---|
1175 | send_to_err += "History (%s) already shared with user (%s)" % ( history.name, send_to_user.email ) |
---|
1176 | else: |
---|
1177 | # Only deal with datasets that have not been purged |
---|
1178 | for hda in history.activatable_datasets: |
---|
1179 | if trans.app.security_agent.can_access_dataset( send_to_user.all_roles(), hda.dataset ): |
---|
1180 | # The no_change_needed dictionary is a special case. If both of can_change |
---|
1181 | # and cannot_change are empty, no_change_needed will used for sharing. Otherwise |
---|
1182 | # unique_no_change_needed will be used for displaying, so we need to populate both. |
---|
1183 | # Build the dictionaries for display, containing unique histories only |
---|
1184 | if history not in unique_no_change_needed: |
---|
1185 | unique_no_change_needed[ history ] = [ hda ] |
---|
1186 | else: |
---|
1187 | unique_no_change_needed[ history ].append( hda ) |
---|
1188 | # Build the dictionaries for sharing |
---|
1189 | if send_to_user not in no_change_needed: |
---|
1190 | no_change_needed[ send_to_user ] = {} |
---|
1191 | if history not in no_change_needed[ send_to_user ]: |
---|
1192 | no_change_needed[ send_to_user ][ history ] = [ hda ] |
---|
1193 | else: |
---|
1194 | no_change_needed[ send_to_user ][ history ].append( hda ) |
---|
1195 | else: |
---|
1196 | # The user with which we are sharing the history does not have access permission on the current dataset |
---|
1197 | if trans.app.security_agent.can_manage_dataset( user_roles, hda.dataset ): |
---|
1198 | # The current user has authority to change permissions on the current dataset because |
---|
1199 | # they have permission to manage permissions on the dataset. |
---|
1200 | # NOTE: ( gvk )There may be problems if the dataset also has an ldda, but I don't think so |
---|
1201 | # because the user with which we are sharing will not have the "manage permission" permission |
---|
1202 | # on the dataset in their history. Keep an eye on this though... |
---|
1203 | if unique: |
---|
1204 | # Build the dictionaries for display, containing unique histories only |
---|
1205 | if history not in can_change: |
---|
1206 | can_change[ history ] = [ hda ] |
---|
1207 | else: |
---|
1208 | can_change[ history ].append( hda ) |
---|
1209 | else: |
---|
1210 | # Build the dictionaries for sharing |
---|
1211 | if send_to_user not in can_change: |
---|
1212 | can_change[ send_to_user ] = {} |
---|
1213 | if history not in can_change[ send_to_user ]: |
---|
1214 | can_change[ send_to_user ][ history ] = [ hda ] |
---|
1215 | else: |
---|
1216 | can_change[ send_to_user ][ history ].append( hda ) |
---|
1217 | else: |
---|
1218 | if action in [ "private", "public" ]: |
---|
1219 | # The user has made a choice, so 'unique' doesn't apply. Don't change stuff |
---|
1220 | # that the user doesn't have permission to change |
---|
1221 | continue |
---|
1222 | if unique: |
---|
1223 | # Build the dictionaries for display, containing unique histories only |
---|
1224 | if history not in cannot_change: |
---|
1225 | cannot_change[ history ] = [ hda ] |
---|
1226 | else: |
---|
1227 | cannot_change[ history ].append( hda ) |
---|
1228 | else: |
---|
1229 | # Build the dictionaries for sharing |
---|
1230 | if send_to_user not in cannot_change: |
---|
1231 | cannot_change[ send_to_user ] = {} |
---|
1232 | if history not in cannot_change[ send_to_user ]: |
---|
1233 | cannot_change[ send_to_user ][ history ] = [ hda ] |
---|
1234 | else: |
---|
1235 | cannot_change[ send_to_user ][ history ].append( hda ) |
---|
1236 | return can_change, cannot_change, no_change_needed, unique_no_change_needed, send_to_err |
---|
1237 | def _share_histories( self, trans, user, send_to_err, histories={} ): |
---|
1238 | # histories looks like: { userA: [ historyX, historyY ], userB: [ historyY ] } |
---|
1239 | msg = "" |
---|
1240 | sent_to_emails = [] |
---|
1241 | for send_to_user in histories.keys(): |
---|
1242 | sent_to_emails.append( send_to_user.email ) |
---|
1243 | emails = ",".join( e for e in sent_to_emails ) |
---|
1244 | if not histories: |
---|
1245 | send_to_err += "No users have been specified or no histories can be sent without changing permissions or associating a sharing role. " |
---|
1246 | else: |
---|
1247 | for send_to_user, send_to_user_histories in histories.items(): |
---|
1248 | shared_histories = [] |
---|
1249 | for history in send_to_user_histories: |
---|
1250 | share = trans.app.model.HistoryUserShareAssociation() |
---|
1251 | share.history = history |
---|
1252 | share.user = send_to_user |
---|
1253 | trans.sa_session.add( share ) |
---|
1254 | self.create_item_slug( trans.sa_session, history ) |
---|
1255 | trans.sa_session.flush() |
---|
1256 | if history not in shared_histories: |
---|
1257 | shared_histories.append( history ) |
---|
1258 | if send_to_err: |
---|
1259 | msg += send_to_err |
---|
1260 | return self.sharing( trans, histories=shared_histories, msg=msg ) |
---|
1261 | |
---|
1262 | @web.expose |
---|
1263 | @web.require_login( "rename histories" ) |
---|
1264 | def rename( self, trans, id=None, name=None, **kwd ): |
---|
1265 | user = trans.get_user() |
---|
1266 | if not id: |
---|
1267 | # Default to the current history |
---|
1268 | history = trans.get_history() |
---|
1269 | if not history.user: |
---|
1270 | return trans.show_error_message( "You must save your history before renaming it." ) |
---|
1271 | id = trans.security.encode_id( history.id ) |
---|
1272 | id = util.listify( id ) |
---|
1273 | name = util.listify( name ) |
---|
1274 | histories = [] |
---|
1275 | cur_names = [] |
---|
1276 | for history_id in id: |
---|
1277 | history = self.get_history( trans, history_id ) |
---|
1278 | if history and history.user_id == user.id: |
---|
1279 | histories.append( history ) |
---|
1280 | cur_names.append( history.get_display_name() ) |
---|
1281 | if not name or len( histories ) != len( name ): |
---|
1282 | return trans.fill_template( "/history/rename.mako", histories=histories ) |
---|
1283 | change_msg = "" |
---|
1284 | for i in range(len(histories)): |
---|
1285 | if histories[i].user_id == user.id: |
---|
1286 | if name[i] == histories[i].get_display_name(): |
---|
1287 | change_msg = change_msg + "<p>History: "+cur_names[i]+" is already named: "+name[i]+"</p>" |
---|
1288 | elif name[i] not in [None,'',' ']: |
---|
1289 | name[i] = escape(name[i]) |
---|
1290 | histories[i].name = name[i] |
---|
1291 | trans.sa_session.add( histories[i] ) |
---|
1292 | trans.sa_session.flush() |
---|
1293 | change_msg = change_msg + "<p>History: "+cur_names[i]+" renamed to: "+name[i]+"</p>" |
---|
1294 | trans.log_event( "History renamed: id: %s, renamed to: '%s'" % (str(histories[i].id), name[i] ) ) |
---|
1295 | else: |
---|
1296 | change_msg = change_msg + "<p>You must specify a valid name for History: "+cur_names[i]+"</p>" |
---|
1297 | else: |
---|
1298 | change_msg = change_msg + "<p>History: "+cur_names[i]+" does not appear to belong to you.</p>" |
---|
1299 | return trans.show_message( "<p>%s" % change_msg, refresh_frames=['history'] ) |
---|
1300 | |
---|
1301 | @web.expose |
---|
1302 | @web.require_login( "clone shared Galaxy history" ) |
---|
1303 | def clone( self, trans, id=None, **kwd ): |
---|
1304 | """Clone a list of histories""" |
---|
1305 | params = util.Params( kwd ) |
---|
1306 | # If clone_choice was not specified, display form passing along id |
---|
1307 | # argument |
---|
1308 | clone_choice = params.get( 'clone_choice', None ) |
---|
1309 | if not clone_choice: |
---|
1310 | return trans.fill_template( "/history/clone.mako", id_argument=id ) |
---|
1311 | # Extract histories for id argument, defaulting to current |
---|
1312 | if id is None: |
---|
1313 | histories = [ trans.history ] |
---|
1314 | else: |
---|
1315 | ids = util.listify( id ) |
---|
1316 | histories = [] |
---|
1317 | for history_id in ids: |
---|
1318 | history = self.get_history( trans, history_id, check_ownership=False ) |
---|
1319 | histories.append( history ) |
---|
1320 | user = trans.get_user() |
---|
1321 | for history in histories: |
---|
1322 | if history.user == user: |
---|
1323 | owner = True |
---|
1324 | else: |
---|
1325 | if trans.sa_session.query( trans.app.model.HistoryUserShareAssociation ) \ |
---|
1326 | .filter_by( user=user, history=history ) \ |
---|
1327 | .count() == 0: |
---|
1328 | return trans.show_error_message( "The history you are attempting to clone is not owned by you or shared with you. " ) |
---|
1329 | owner = False |
---|
1330 | name = "Clone of '%s'" % history.name |
---|
1331 | if not owner: |
---|
1332 | name += " shared by '%s'" % history.user.email |
---|
1333 | if clone_choice == 'activatable': |
---|
1334 | new_history = history.copy( name=name, target_user=user, activatable=True ) |
---|
1335 | elif clone_choice == 'active': |
---|
1336 | name += " (active items only)" |
---|
1337 | new_history = history.copy( name=name, target_user=user ) |
---|
1338 | if len( histories ) == 1: |
---|
1339 | msg = 'Clone with name "%s" is now included in your previously stored histories.' % new_history.name |
---|
1340 | else: |
---|
1341 | msg = '%d cloned histories are now included in your previously stored histories.' % len( histories ) |
---|
1342 | return trans.show_ok_message( msg ) |
---|