# HG changeset patch -- Bitbucket.org # Project galaxy-dist # URL http://bitbucket.org/galaxy/galaxy-dist/overview # User Greg Von Kuster <greg@bx.psu.edu> # Date 1286829357 14400 # Node ID 4bc2738671123bf3731e940ae1f2a0004ceca2be # Parent b67978eeba2c67c86453515d90f81a13acb88586 First pass at Galaxy sample tracking re-write. Highlights: lots of code cleanup with several not-needed methods removed, many mthods and mako templates have been renamed clarify what they do, all object ids are now encoded, methods in the requests_common controller that include features requiring an admin user are now secured. More work is needed in the requests_admin controller which will be in the next commit. --- a/templates/requests/common/sample_events.mako +++ b/templates/requests/common/sample_events.mako @@ -6,7 +6,7 @@ <h2>Events for Sample "${sample.name}"</h2><ul class="manage-table-actions"><li> - <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='show', id=trans.security.encode_id(sample.request.id) )}"> + <a class="action-button" href="${h.url_for( controller='requests_common', action='manage_request', cntrller=cntrller, id=trans.security.encode_id( sample.request.id ) )}"><span>Browse this request</span></a></li></ul> --- a/templates/library/common/browse_library.mako +++ b/templates/library/common/browse_library.mako @@ -471,7 +471,7 @@ <h2>Data Library “${library.name}”</h2><ul class="manage-table-actions"> - %if not library.deleted and ( ( trans.user_is_admin() and cntrller == 'requests_admin' ) or can_add ): + %if not library.deleted and ( ( trans.user_is_admin() and cntrller == 'library_admin' ) or can_add ): <li><a class="action-button" href="${h.url_for( controller='library_common', action='upload_library_dataset', cntrller=cntrller, library_id=trans.security.encode_id( library.id ), folder_id=trans.security.encode_id( library.root_folder.id ), use_panels=use_panels, show_deleted=show_deleted )}"><span>Add datasets</span></a></li><li><a class="action-button" href="${h.url_for( controller='library_common', action='create_folder', cntrller=cntrller, parent_id=trans.security.encode_id( library.root_folder.id ), library_id=trans.security.encode_id( library.id ), use_panels=use_panels, show_deleted=show_deleted )}">Add folder</a></li> %endif --- /dev/null +++ b/templates/requests/find_samples_index.mako @@ -0,0 +1,14 @@ +<%inherit file="/webapps/galaxy/base_panels.mako"/> + +<%def name="init()"> + <% + self.has_left_panel=False + self.has_right_panel=False + self.active_view="requests" + self.message_box_visible=False + %> +</%def> + +<%def name="center_panel()"> + <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${h.url_for( controller='requests_common', action='find_samples' )}"></iframe> +</%def> --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -856,9 +856,9 @@ APIKeys.table = Table( "api_keys", metad assign_mapper( context, Sample, Sample.table, properties=dict( events=relation( SampleEvent, backref="sample", - order_by=desc(SampleEvent.table.c.update_time) ), + order_by=desc( SampleEvent.table.c.update_time ) ), datasets=relation( SampleDataset, backref="sample", - order_by=desc(SampleDataset.table.c.update_time) ), + order_by=desc( SampleDataset.table.c.update_time ) ), values=relation( FormValues, primaryjoin=( Sample.table.c.form_values_id == FormValues.table.c.id ) ), request=relation( Request, @@ -885,9 +885,9 @@ assign_mapper( context, Request, Request backref="requests" ), samples=relation( Sample, primaryjoin=( Request.table.c.id == Sample.table.c.request_id ), - order_by=asc(Sample.table.c.id) ), + order_by=asc( Sample.table.c.id ) ), events=relation( RequestEvent, backref="request", - order_by=desc(RequestEvent.table.c.update_time) ) + order_by=desc( RequestEvent.table.c.update_time ) ) ) ) assign_mapper( context, RequestEvent, RequestEvent.table, @@ -897,7 +897,7 @@ assign_mapper( context, RequestType, Req properties=dict( states=relation( SampleState, backref="request_type", primaryjoin=( RequestType.table.c.id == SampleState.table.c.request_type_id ), - order_by=asc(SampleState.table.c.update_time) ), + order_by=asc( SampleState.table.c.update_time ) ), request_form=relation( FormDefinition, primaryjoin=( RequestType.table.c.request_form_id == FormDefinition.table.c.id ) ), sample_form=relation( FormDefinition, --- a/templates/admin/requests/view_request_type.mako +++ b/templates/admin/requests/view_request_type.mako @@ -1,11 +1,12 @@ <%inherit file="/base.mako"/><%namespace file="/message.mako" import="render_msg" /> - %if message: ${render_msg( message, status )} %endif +<% states_list = request_type.states %> + <h2>Sequencer Configuration "${request_type.name}"</h2><div class="toolForm"> --- a/lib/galaxy/web/controllers/requests_admin.py +++ b/lib/galaxy/web/controllers/requests_admin.py @@ -1,163 +1,38 @@ from galaxy.web.base.controller import * from galaxy.web.framework.helpers import time_ago, iff, grids from galaxy.model.orm import * -from galaxy.datatypes import sniff from galaxy import model, util -from galaxy.util.streamball import StreamBall -import logging, tempfile, zipfile, tarfile, os, sys, subprocess, smtplib, socket -from galaxy.web.form_builder import * -from datetime import datetime, timedelta -from email.MIMEText import MIMEText -from sqlalchemy.sql.expression import func, and_ -from sqlalchemy.sql import select -import pexpect -import ConfigParser, threading, time +from galaxy.web.form_builder import * +from galaxy.web.controllers.requests_common import RequestsGrid, invalid_id_redirect from amqplib import client_0_8 as amqp -import csv +import logging, os, pexpect, ConfigParser + log = logging.getLogger( __name__ ) -# -# ---- Request Grid ------------------------------------------------------------ -# +class UserColumn( grids.TextColumn ): + def get_value( self, trans, grid, request ): + return request.user.email -class RequestsGrid( grids.Grid ): - # Custom column types - class NameColumn( grids.TextColumn ): - def get_value(self, trans, grid, request): - return request.name - class DescriptionColumn( grids.TextColumn ): - def get_value(self, trans, grid, request): - return request.desc - class SamplesColumn( grids.GridColumn ): - def get_value(self, trans, grid, request): - return str(len(request.samples)) - class TypeColumn( grids.TextColumn ): - def get_value(self, trans, grid, request): - return request.type.name - class StateColumn( grids.GridColumn ): - def __init__( self, col_name, key, model_class, event_class, filterable, link ): - grids.GridColumn.__init__(self, col_name, key=key, model_class=model_class, filterable=filterable, link=link) - self.event_class = event_class - def get_value(self, trans, grid, request): - if request.state() == request.states.REJECTED: - return '<div class="count-box state-color-error">%s</div>' % request.state() - elif request.state() == request.states.NEW: - return '<div class="count-box state-color-queued">%s</div>' % request.state() - elif request.state() == request.states.SUBMITTED: - return '<div class="count-box state-color-running">%s</div>' % request.state() - elif request.state() == request.states.COMPLETE: - return '<div class="count-box state-color-ok">%s</div>' % request.state() - return request.state() - def filter( self, trans, user, query, column_filter ): - """ Modify query to filter request by state. """ - if column_filter == "All": - return query - if column_filter: - # select r.id, r.name, re.id, re.state - # from request as r, request_event as re - # where re.request_id=r.id and re.state='Complete' and re.create_time in - # (select MAX( create_time) - # from request_event - # group by request_id) - q = query.join(self.event_class.table)\ - .filter( self.model_class.table.c.id==self.event_class.table.c.request_id )\ - .filter( self.event_class.table.c.state==column_filter )\ - .filter( self.event_class.table.c.id.in_(select(columns=[func.max(self.event_class.table.c.id)], - from_obj=self.event_class.table, - group_by=self.event_class.table.c.request_id))) - return q - def get_accepted_filters( self ): - """ Returns a list of accepted filters for this column. """ - accepted_filter_labels_and_vals = [ model.Request.states.NEW, - model.Request.states.REJECTED, - model.Request.states.SUBMITTED, - model.Request.states.COMPLETE, - "All"] - accepted_filters = [] - for val in accepted_filter_labels_and_vals: - label = val.lower() - args = { self.key: val } - accepted_filters.append( grids.GridColumnFilter( label, args) ) - return accepted_filters - class UserColumn( grids.TextColumn ): - def get_value(self, trans, grid, request): - return request.user.email - def sort( self, query, ascending ): - """ Sort column using case-insensitive alphabetical sorting on item's username. """ - if ascending: - query = query.order_by( func.lower ( self.model_class.email ).asc() ) - else: - query = query.order_by( func.lower( self.model_class.email ).desc() ) - return query - - # Grid definition - title = "Sequencing Requests" - template = "admin/requests/grid.mako" - model_class = model.Request - default_sort_key = "-update_time" - num_rows_per_page = 50 - preserve_state = True - use_paging = True - default_filter = dict( deleted="False") - columns = [ - NameColumn( "Name", - key="name", - model_class=model.Request, - link=( lambda item: iff( item.deleted, None, dict( operation="show", id=item.id ) ) ), - attach_popup=True, - filterable="advanced" ), - DescriptionColumn( "Description", - key='desc', - model_class=model.Request, - filterable="advanced" ), - SamplesColumn( "Sample(s)", - link=( lambda item: iff( item.deleted, None, dict( operation="show", id=item.id ) ) ), ), - TypeColumn( "Sequencer", - link=( lambda item: iff( item.deleted, None, dict( operation="view_type", id=item.type.id ) ) ), ), - grids.GridColumn( "Last Updated", key="update_time", format=time_ago ), - grids.DeletedColumn( "Deleted", - key="deleted", - visible=False, - filterable="advanced" ), - StateColumn( "State", - key='state', - model_class=model.Request, - event_class=model.RequestEvent, - filterable="advanced", - link=( lambda item: iff( item.deleted, None, dict( operation="events", id=item.id ) ) ), - ), - UserColumn( "User", - #key='owner', - model_class=model.User - #filterable="advanced" - ) - - ] - columns.append( grids.MulticolFilterColumn( "Search", - cols_to_filter=[ columns[0], columns[1], columns[6] ], - key="free-text-search", - visible=False, - filterable="standard" ) ) - operations = [ - grids.GridOperation( "Submit", allow_multiple=False, condition=( lambda item: not item.deleted and item.unsubmitted() and item.samples ), - confirm="More samples cannot be added to this request once it is submitted. Click OK to submit." ), - grids.GridOperation( "Edit", allow_multiple=False, condition=( lambda item: not item.deleted ) ), - grids.GridOperation( "Reject", allow_multiple=False, condition=( lambda item: not item.deleted and item.submitted() ) ), - grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: not item.deleted ) ), - grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ), - grids.GridOperation( "Purge", allow_multiple=False, confirm="This will permanently delete the sequencing request. Click OK to proceed.", condition=( lambda item: item.deleted ) ), - ] +class AdminRequestsGrid( RequestsGrid ): + columns = [ col for col in RequestsGrid.columns ] + columns.append( UserColumn( "User", + model_class=model.User, + key='username' ) ) + operations = [ operation for operation in RequestsGrid.operations ] + operations.append( grids.GridOperation( "Edit", allow_multiple=False, condition=( lambda item: not item.deleted ) ) ) + operations.append( grids.GridOperation( "Reject", allow_multiple=False, condition=( lambda item: not item.deleted and item.is_submitted ) ) ) + operations.append( grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: not item.deleted ) ) ) + operations.append( grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ) ) + operations.append( grids.GridOperation( "Purge", + allow_multiple=False, + confirm="This will permanently delete this sequencing request. Click OK to proceed.", + condition=( lambda item: item.deleted ) ) ) global_actions = [ grids.GridAction( "Create new request", dict( controller='requests_common', - cntrller='requests_admin', - action='new', - select_request_type='True' ) ) + action='create_request', + cntrller='requests_admin' ) ) ] - -# -# ---- Request Type Grid ------------------------------------------------------ -# class RequestTypeGrid( grids.Grid ): # Custom column types class NameColumn( grids.TextColumn ): @@ -172,9 +47,10 @@ class RequestTypeGrid( grids.Grid ): class SampleFormColumn( grids.TextColumn ): def get_value(self, trans, grid, request_type): return request_type.sample_form.name + # Grid definition title = "Sequencer Configurations" - template = "admin/requests/manage_request_types.mako" + template = "admin/requests/grid.mako" model_class = model.RequestType default_sort_key = "-create_time" num_rows_per_page = 50 @@ -183,23 +59,21 @@ class RequestTypeGrid( grids.Grid ): default_filter = dict( deleted="False" ) columns = [ NameColumn( "Name", - key="name", - model_class=model.RequestType, + key="name", link=( lambda item: iff( item.deleted, None, dict( operation="view", id=item.id ) ) ), attach_popup=True, filterable="advanced" ), DescriptionColumn( "Description", key='desc', - model_class=model.Request, filterable="advanced" ), RequestFormColumn( "Request Form", - link=( lambda item: iff( item.deleted, None, dict( operation="view_form", id=item.request_form.id ) ) ), ), + link=( lambda item: iff( item.deleted, None, dict( operation="view_form", id=item.request_form.id ) ) ) ), SampleFormColumn( "Sample Form", - link=( lambda item: iff( item.deleted, None, dict( operation="view_form", id=item.sample_form.id ) ) ), ), + link=( lambda item: iff( item.deleted, None, dict( operation="view_form", id=item.sample_form.id ) ) ) ), grids.DeletedColumn( "Deleted", - key="deleted", - visible=False, - filterable="advanced" ) + key="deleted", + visible=False, + filterable="advanced" ) ] columns.append( grids.MulticolFilterColumn( "Search", cols_to_filter=[ columns[0], columns[1] ], @@ -208,53 +82,45 @@ class RequestTypeGrid( grids.Grid ): filterable="standard" ) ) operations = [ grids.GridOperation( "Permissions", allow_multiple=False, condition=( lambda item: not item.deleted ) ), - #grids.GridOperation( "Clone", allow_multiple=False, condition=( lambda item: not item.deleted ) ), grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: not item.deleted ) ), grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ), ] global_actions = [ - grids.GridAction( "Create new sequencer configuration", dict( controller='requests_admin', - action='create_request_type' ) ) + grids.GridAction( "Create new sequencer configuration", dict( controller='requests_admin', action='create_request_type' ) ) ] - -# ---- Data Transfer Grid ------------------------------------------------------ -# class DataTransferGrid( grids.Grid ): # Custom column types class NameColumn( grids.TextColumn ): - def get_value(self, trans, grid, sample_dataset): + def get_value( self, trans, grid, sample_dataset ): return sample_dataset.name class SizeColumn( grids.TextColumn ): - def get_value(self, trans, grid, sample_dataset): + def get_value( self, trans, grid, sample_dataset ): return sample_dataset.size class StatusColumn( grids.TextColumn ): - def get_value(self, trans, grid, sample_dataset): + def get_value( self, trans, grid, sample_dataset ): return sample_dataset.status # Grid definition title = "Sample Datasets" + # TODO: can this be grid.mako??? template = "admin/requests/datasets_grid.mako" model_class = model.SampleDataset default_sort_key = "-create_time" num_rows_per_page = 50 preserve_state = True use_paging = True - #default_filter = dict( deleted="False" ) columns = [ NameColumn( "Name", #key="name", - model_class=model.SampleDataset, link=( lambda item: dict( operation="view", id=item.id ) ), attach_popup=True, filterable="advanced" ), SizeColumn( "Size", #key='size', - model_class=model.SampleDataset, filterable="advanced" ), grids.GridColumn( "Last Updated", key="update_time", format=time_ago ), StatusColumn( "Status", #key='status', - model_class=model.SampleDataset, filterable="advanced" ), ] columns.append( grids.MulticolFilterColumn( "Search", @@ -263,517 +129,443 @@ class DataTransferGrid( grids.Grid ): visible=False, filterable="standard" ) ) operations = [ - grids.GridOperation( "Start Transfer", allow_multiple=True, condition=( lambda item: item.status in [model.Sample.transfer_status.NOT_STARTED] ) ), - grids.GridOperation( "Rename", allow_multiple=True, allow_popup=False, condition=( lambda item: item.status in [model.Sample.transfer_status.NOT_STARTED] ) ), - grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: item.status in [model.Sample.transfer_status.NOT_STARTED] ) ), + grids.GridOperation( "Start Transfer", allow_multiple=True, condition=( lambda item: item.status in [ model.Sample.transfer_status.NOT_STARTED ] ) ), + grids.GridOperation( "Rename", allow_multiple=True, allow_popup=False, condition=( lambda item: item.status in [ model.Sample.transfer_status.NOT_STARTED ] ) ), + grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: item.status in [ model.Sample.transfer_status.NOT_STARTED ] ) ), ] def apply_query_filter( self, trans, query, **kwd ): - return query.filter_by( sample_id=kwd['sample_id'] ) -# -# ---- Request Controller ------------------------------------------------------ -# + sample_id = kwd.get( 'sample_id', None ) + if not sample_id: + return query + return query.filter_by( sample_id=trans.security.decode_id( sample_id ) ) class RequestsAdmin( BaseController, UsesFormDefinitionWidgets ): - request_grid = RequestsGrid() + request_grid = AdminRequestsGrid() requesttype_grid = RequestTypeGrid() datatx_grid = DataTransferGrid() - @web.expose @web.require_admin def index( self, trans ): return trans.fill_template( "/admin/requests/index.mako" ) - @web.expose @web.require_admin - def list( self, trans, **kwd ): - ''' - List all request made by the current user - ''' + def browse_requests( self, trans, **kwd ): if 'operation' in kwd: operation = kwd['operation'].lower() - if not kwd.get( 'id', None ): - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message="Invalid request ID") ) - if operation == "show": + if operation == "edit": return trans.response.send_redirect( web.url_for( controller='requests_common', + action='edit_basic_request_info', cntrller='requests_admin', - action='show', **kwd ) ) - elif operation == "submit": + if operation == "manage_request": return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', cntrller='requests_admin', - action='submit', **kwd ) ) - elif operation == "delete": + if operation == "reject": + return self.reject_request( trans, **kwd ) + if operation == "view_type": + return self.view_request_type( trans, **kwd ) + if operation == "delete": return trans.response.send_redirect( web.url_for( controller='requests_common', + action='delete_request', cntrller='requests_admin', - action='delete', **kwd ) ) - elif operation == "undelete": + if operation == "undelete": return trans.response.send_redirect( web.url_for( controller='requests_common', + action='undelete_request', cntrller='requests_admin', - action='undelete', **kwd ) ) - elif operation == "edit": - return trans.response.send_redirect( web.url_for( controller='requests_common', - cntrller='requests_admin', - action='edit', - show=True, **kwd ) ) - elif operation == "events": - return trans.response.send_redirect( web.url_for( controller='requests_common', - cntrller='requests_admin', - action='events', - **kwd ) ) - elif operation == "reject": - return self.__reject_request( trans, **kwd ) - elif operation == "view_type": - return self.__view_request_type( trans, **kwd ) - # Render the grid view + # Render the list view return self.request_grid( trans, **kwd ) - @web.json - def get_file_details( self, trans, id=None, folder_path=None ): - def print_ticks(d): + def get_file_details( self, trans, id, folder_path ): + def print_ticks( d ): + # TODO: why is this method here? Add comments! pass # Avoid caching trans.response.headers['Pragma'] = 'no-cache' trans.response.headers['Expires'] = '0' - request = trans.sa_session.query( self.app.model.Request ).get( int(id) ) + request = trans.sa_session.query( trans.model.Request ).get( int( id ) ) datatx_info = request.type.datatx_info cmd = 'ssh %s@%s "ls -oghp \'%s\'"' % ( datatx_info['username'], datatx_info['host'], - folder_path ) - output = pexpect.run(cmd, events={'.ssword:*': datatx_info['password']+'\r\n', - pexpect.TIMEOUT:print_ticks}, - timeout=10) - return unicode(output.replace('\n', '<br/>')) - + folder_path ) + output = pexpect.run( cmd, + events={ '.ssword:*' : datatx_info[ 'password'] + '\r\n', pexpect.TIMEOUT : print_ticks }, + timeout=10 ) + return unicode( output.replace( '\n', '<br/>' ) ) @web.json - def open_folder( self, trans, id=None, folder_path=None ): - def print_ticks(d): + def open_folder( self, trans, id, folder_path ): + def print_ticks( d ): pass # Avoid caching trans.response.headers['Pragma'] = 'no-cache' trans.response.headers['Expires'] = '0' - request = trans.sa_session.query( self.app.model.Request ).get( int(id) ) - return self.__get_files(trans, request.type, folder_path) - - def __reject_request(self, trans, **kwd): - try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) - except: - message = "Invalid request ID" - log.warn( message ) - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message=message, - **kwd) ) - return trans.fill_template( '/admin/requests/reject.mako', - request=request) + request = trans.sa_session.query( trans.model.Request ).get( int( id ) ) + return self.__get_files( trans, request.type, folder_path ) @web.expose @web.require_admin - def reject(self, trans, **kwd): + def reject( self, trans, **kwd ): params = util.Params( kwd ) - if params.get('cancel_reject_button', False): - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - operation='show_request', - id=kwd['id'])) + request_id = params.get( 'id', '' ) + status = params.get( 'status', 'done' ) + message = params.get( 'message', 'done' ) + if params.get( 'cancel_reject_button', False ): + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', + cntrller='requests_admin', + id=request_id ) ) try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) + request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) ) except: - message = "Invalid request ID" - log.warn( message ) - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message=message, - **kwd) ) - # validate - if not params.get('comment', ''): + return invalid_id_redirect( trans, 'requests_admin', request_id ) + # Validate + comment = util.restore_text( params.get( 'comment', '' ) ) + if not comment: + status='error' + message='A reason for rejecting the request is required.' return trans.fill_template( '/admin/requests/reject.mako', - request=request, status='error', - message='A comment is required for rejecting a request.') - # create an event with state 'Rejected' for this request - comments = util.restore_text( params.comment ) - event = trans.app.model.RequestEvent(request, request.states.REJECTED, comments) + cntrller='requests_admin', + request=request, + status=status, + message=message ) + # Create an event with state 'Rejected' for this request + event = trans.model.RequestEvent( request, request.states.REJECTED, comment ) trans.sa_session.add( event ) trans.sa_session.flush() + message='Request (%s) has been rejected.' % request.name return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='done', - message='Request <b>%s</b> has been rejected.' % request.name) ) - # + action='browse_requests', + status=status, + message=message, + **kwd ) ) # Data transfer from sequencer - # - @web.expose @web.require_admin def manage_datasets( self, trans, **kwd ): if 'operation' in kwd: - operation = kwd['operation'].lower() - if not kwd.get( 'id', None ): - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message="Invalid sample dataset ID") ) + operation = kwd[ 'operation' ].lower() + dataset_id = kwd.get( 'id', None ) + if not dataset_id: + return invalid_id_redirect( trans, 'requests_admin', dataset_id ) + id_list = util.listify( dataset_id ) if operation == "view": - sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(kwd['id']) ) - return trans.fill_template( '/admin/requests/dataset.mako', - sample=sample_dataset.sample, - sample_dataset=sample_dataset) - + sample_dataset_id = trans.security.decode_id( kwd['id'] ) + sample_dataset = trans.sa_session.query( trans.model.SampleDataset ).get( sample_dataset_id ) + return trans.fill_template( '/admin/requests/dataset.mako', + sample_dataset=sample_dataset ) elif operation == "delete": - id_list = util.listify( kwd['id'] ) not_deleted = [] for id in id_list: - sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(id) ) + sample_dataset = trans.sa_session.query( trans.model.SampleDataset ).get( trans.security.decode_id( id ) ) sample_id = sample_dataset.sample_id if sample_dataset.status == sample_dataset.sample.transfer_status.NOT_STARTED: trans.sa_session.delete( sample_dataset ) trans.sa_session.flush() else: - not_deleted.append(sample_dataset.name) - message = '%i dataset(s) have been successfully deleted. ' % (len(id_list) - len(not_deleted)) + not_deleted.append( sample_dataset.name ) + message = '%i datasets have been successfully deleted. ' % ( len( id_list ) - len( not_deleted ) ) status = 'done' if not_deleted: status = 'warning' - message = message + '%s could not be deleted. Only datasets with transfer status "Not Started" can be deleted. ' % str(not_deleted) + message = message + '%s could not be deleted because their transfer status is not "Not Started". ' % str( not_deleted ) return trans.response.send_redirect( web.url_for( controller='requests_admin', action='manage_datasets', - sample_id=sample_id, + sample_id=trans.security.encode_id( sample_id ), status=status, - message=message) ) - + message=message ) ) elif operation == "rename": - id_list = util.listify( kwd['id'] ) - sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(id_list[0]) ) + sample_dataset_id = id_list[0] + sample_dataset = trans.sa_session.query( trans.model.SampleDataset ).get( trans.security.decode_id( sample_dataset_id ) ) return trans.fill_template( '/admin/requests/rename_datasets.mako', sample=sample_dataset.sample, id_list=id_list ) elif operation == "start transfer": - id_list = util.listify( kwd['id'] ) - sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(id_list[0]) ) - self.__start_datatx(trans, sample_dataset.sample, id_list) - - + sample_dataset_id = id_list[0] + sample_dataset = trans.sa_session.query( trans.model.SampleDataset ).get( trans.security.decode_id( sample_dataset_id ) ) + self.__start_datatx( trans, sample_dataset.sample, id_list ) # Render the grid view + sample_id = kwd.get( 'sample_id', None ) try: - sample = trans.sa_session.query( trans.app.model.Sample ).get( kwd['sample_id'] ) + sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id ( sample_id ) ) except: - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message="Invalid sample ID" ) ) - self.datatx_grid.title = 'Datasets of Sample "%s"' % sample.name - self.datatx_grid.global_actions = [ - grids.GridAction( "Refresh", - dict( controller='requests_admin', - action='manage_datasets', - sample_id=sample.id) ), - grids.GridAction( "Select Datasets", - dict(controller='requests_admin', - action='get_data', - request_id=sample.request.id, - folder_path=sample.request.type.datatx_info['data_dir'], - sample_id=sample.id, - show_page=True)), - grids.GridAction( 'Data Library "%s"' % sample.library.name, - dict(controller='library_common', - action='browse_library', - cntrller='library_admin', - id=trans.security.encode_id( sample.library.id))), - grids.GridAction( "Browse this request", - dict( controller='requests_admin', - action='list', - operation='show', - id=trans.security.encode_id(sample.request.id)))] + return invalid_id_redirect( trans, 'requests_admin', sample_id ) + self.datatx_grid.title = 'Datasets of sample "%s"' % sample.name + self.datatx_grid.global_actions = [ grids.GridAction( "Refresh", + dict( controller='requests_admin', + action='manage_datasets', + sample_id=sample_id ) ), + grids.GridAction( "Select datasets", + dict( controller='requests_admin', + action='get_data', + request_id=trans.security.encode_id( sample.request.id ), + folder_path=sample.request.type.datatx_info[ 'data_dir' ], + sample_id=sample_id, + show_page=True ) ), + grids.GridAction( 'Data library "%s"' % sample.library.name, + dict( controller='library_common', + action='browse_library', + cntrller='library_admin', + id=trans.security.encode_id( sample.library.id ) ) ), + grids.GridAction( "Browse this request", + dict( controller='requests_common', + action='manage_request', + cntrller='requests_admin', + id=trans.security.encode_id( sample.request.id ) ) ) ] return self.datatx_grid( trans, **kwd ) - @web.expose @web.require_admin def rename_datasets( self, trans, **kwd ): params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) + status = params.get( 'status', 'done' ) + sample_id = kwd.get( 'sample_id', None ) try: - sample = trans.sa_session.query( trans.app.model.Sample ).get( trans.security.decode_id(kwd['sample_id'])) + sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) ) except: - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message="Invalid sample ID" ) ) + return invalid_id_redirect( trans, 'requests_admin', sample_id ) if params.get( 'save_button', False ): id_list = util.listify( kwd['id_list'] ) for id in id_list: - sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(id) ) + sample_dataset = trans.sa_session.query( trans.model.SampleDataset ).get( trans.security.decode_id( id ) ) prepend = util.restore_text( params.get( 'prepend_%i' % sample_dataset.id, '' ) ) - name = util.restore_text( params.get( 'name_%i' % sample_dataset.id, sample_dataset.name ) ) + name = util.restore_text( params.get( 'name_%i' % sample_dataset.id, sample_dataset.name ) ) if prepend == 'None': sample_dataset.name = name else: - sample_dataset.name = prepend+'_'+name + sample_dataset.name = '%s_%s' % ( prepend, name ) trans.sa_session.add( sample_dataset ) trans.sa_session.flush() return trans.fill_template( '/admin/requests/rename_datasets.mako', - sample=sample, id_list=id_list, + sample=sample, + id_list=id_list, message='Changes saved successfully.', status='done' ) return trans.response.send_redirect( web.url_for( controller='requests_admin', action='manage_datasets', - sample_id=sample.id) ) - - - def __get_files(self, trans, request_type, folder_path): - ''' - This method retrieves the filenames to be transfer from the remote host. - ''' + sample_id=sample_id ) ) + def __get_files( self, trans, request_type, folder_path ): + # Retrieves the filenames to be transferred from the remote host. + # FIXME: sample is used in this method, but no sample obj is received... datatx_info = request_type.datatx_info - if not datatx_info['host'] or not datatx_info['username'] or not datatx_info['password']: + if not datatx_info[ 'host' ] or not datatx_info[ 'username' ] or not datatx_info[ 'password' ]: + status = 'error' message = "Error in sequencer login information." return trans.response.send_redirect( web.url_for( controller='requests_common', + action='view_dataset_transfer', cntrller='requests_admin' , - action='show_datatx_page', - sample_id=trans.security.encode_id(sample.id), - status='error', - message=message)) - def print_ticks(d): + sample_id=trans.security.encode_id( sample.id ), + status=status, + message=message ) ) + def print_ticks( d ): pass - cmd = 'ssh %s@%s "ls -p \'%s\'"' % ( datatx_info['username'], - datatx_info['host'], - folder_path) - output = pexpect.run(cmd, events={'.ssword:*': datatx_info['password']+'\r\n', - pexpect.TIMEOUT:print_ticks}, - timeout=10) + cmd = 'ssh %s@%s "ls -p \'%s\'"' % ( datatx_info['username'], datatx_info['host'], folder_path ) + output = pexpect.run( cmd, + events={ '.ssword:*' : datatx_info['password'] + '\r\n', pexpect.TIMEOUT : print_ticks }, + timeout=10 ) if 'No such file or directory' in output: - message = "No such folder (%s) exists on the sequencer." % folder_path + status = 'error' + message = "No folder named (%s) exists on the sequencer." % folder_path return trans.response.send_redirect( web.url_for( controller='requests_common', + action='view_dataset_transfer', cntrller='requests_admin' , - action='show_datatx_page', - sample_id=trans.security.encode_id(sample.id), - message=message, status='error', - folder_path=folder_path )) + sample_id=trans.security.encode_id( sample.id ), + folder_path=folder_path, + status=status, + message=message ) ) return output.splitlines() - -# def __get_files_in_dir(self, trans, sample, folder_path): -# tmpfiles = self.__get_files(trans, sample, folder_path) -# for tf in tmpfiles: -# if tf[-1] == os.sep: -# self.__get_files_in_dir(trans, sample, os.path.join(folder_path, tf)) -# else: -# sample.dataset_files.append([os.path.join(folder_path, tf), -# sample.transfer_status.NOT_STARTED]) -# trans.sa_session.add( sample ) -# trans.sa_session.flush() -# return - - - def __samples_selectbox(self, trans, request, sample_id=None): - samples_selectbox = SelectField('sample_id') - for i, s in enumerate(request.samples): - if str(s.id) == sample_id: - samples_selectbox.add_option(s.name, s.id, selected=True) - else: - samples_selectbox.add_option(s.name, s.id) - return samples_selectbox - + def __check_path( self, a_path ): + # Return a valid folder_path + if a_path and not a_path.endswith( os.sep ): + a_path += os.sep + return a_path + def __build_sample_id_select_field( self, request, selected_value ): + return build_select_field( trans, request.samples, 'name', 'sample_id', selected_value=selected_value, refresh_on_change=False ) @web.expose @web.require_admin - def get_data(self, trans, **kwd): + def get_data( self, trans, **kwd ): params = util.Params( kwd ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + request_id = kwd.get( 'request_id', None ) try: - request = trans.sa_session.query( trans.app.model.Request ).get( kwd['request_id'] ) + request = trans.sa_session.query( trans.model.Request ).get( request_id ) except: - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message="Invalid request ID" ) ) - files_list = util.listify( params.get( 'files_list', '' ) ) - folder_path = util.restore_text( params.get( 'folder_path', - request.type.datatx_info['data_dir'] ) ) - sbox = self.__samples_selectbox(trans, request, kwd.get('sample_id', None)) + return invalid_id_redirect( trans, 'requests_admin', request_id ) + files_list = util.listify( params.get( 'files_list', '' ) ) + folder_path = util.restore_text( params.get( 'folder_path', request.type.datatx_info[ 'data_dir' ] ) ) + selected_value = kwd.get( 'sample_id', 'none' ) + sample_id_select_field = self.__build_sample_id_select_field( request, selected_value ) if not folder_path: - return trans.fill_template( '/admin/requests/get_data.mako', - cntrller='requests_admin', request=request, - samples_selectbox=sbox, files=[], + return trans.fill_template( '/admin/requests/dataset_transfer.mako', + cntrller='requests_admin', + request=request, + sample_id_select_field=sample_id_select_field, + files=[], folder_path=folder_path ) - if folder_path[-1] != os.sep: - folder_path = folder_path+os.sep + folder_path = self.__check_path( folder_path ) + sample_id = kwd.get( 'sample_id', None ) if params.get( 'show_page', False ): - if kwd.get('sample_id', None): - sample = trans.sa_session.query( trans.app.model.Sample ).get( kwd['sample_id'] ) + if sample_id: + sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) ) if sample.datasets: - folder_path = os.path.dirname(sample.datasets[-1].file_path) - return trans.fill_template( '/admin/requests/get_data.mako', - cntrller='requests_admin', request=request, - samples_selectbox=sbox, files=[], + folder_path = os.path.dirname( sample.datasets[-1].file_path ) + return trans.fill_template( '/admin/requests/dataset_transfer.mako', + cntrller='requests_admin', + request=request, + sample_id_select_field=sample_id_select_field, + files=[], folder_path=folder_path, - status=status, message=message ) + status=status, + message=message ) elif params.get( 'browse_button', False ): # get the filenames from the remote host - files = self.__get_files(trans, request.type, folder_path) - if folder_path[-1] != os.sep: - folder_path += os.sep - return trans.fill_template( '/admin/requests/get_data.mako', - cntrller='requests_admin', request=request, - samples_selectbox=sbox, files=files, + files = self.__get_files( trans, request.type, folder_path ) + return trans.fill_template( '/admin/requests/dataset_transfer.mako', + cntrller='requests_admin', + request=request, + sample_id_select_field=sample_id_select_field, + files=files, folder_path=folder_path, - status=status, message=message ) + status=status, + message=message ) elif params.get( 'folder_up', False ): - if folder_path[-1] == os.sep: - folder_path = os.path.dirname(folder_path[:-1]) # get the filenames from the remote host - files = self.__get_files(trans, request.type, folder_path) - if folder_path[-1] != os.sep: - folder_path += os.sep - return trans.fill_template( '/admin/requests/get_data.mako', - cntrller='requests_admin',request=request, - samples_selectbox=sbox, files=files, + files = self.__get_files( trans, request.type, folder_path ) + return trans.fill_template( '/admin/requests/dataset_transfer.mako', + cntrller='requests_admin', + request=request, + sample_id_select_field=sample_id_select_field, + files=files, folder_path=folder_path, - status=status, message=message ) + status=status, + message=message ) elif params.get( 'open_folder', False ): - if len(files_list) == 1: - folder_path = os.path.join(folder_path, files_list[0]) + if len( files_list ) == 1: + folder_path = os.path.join( folder_path, files_list[0] ) + folder_path = self.__check_path( folder_path ) # get the filenames from the remote host - files = self.__get_files(trans, request.type, folder_path) - if folder_path[-1] != os.sep: - folder_path += os.sep - return trans.fill_template( '/admin/requests/get_data.mako', - cntrller='requests_admin', request=request, - samples_selectbox=sbox, files=files, + files = self.__get_files( trans, request.type, folder_path ) + return trans.fill_template( '/admin/requests/dataset_transfer.mako', + cntrller='requests_admin', + request=request, + sample_id_select_field=sample_id_select_field, + files=files, folder_path=folder_path, - status=status, message=message ) + status=status, + message=message ) elif params.get( 'select_show_datasets_button', False ): - sample = trans.sa_session.query( trans.app.model.Sample ).get( kwd['sample_id'] ) - retval = self.__save_sample_datasets(trans, sample, files_list, folder_path) - if retval: message='The dataset(s) %s have been selected for sample <b>%s</b>' %(str(retval)[1:-1].replace("'", ""), sample.name) - else: message = None + sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) ) + retval = self.__save_sample_datasets( trans, sample, files_list, folder_path ) + if retval: + message = 'The datasets %s have been selected for sample <b>%s</b>' % ( str( retval )[1:-1].replace( "'", "" ), sample.name ) return trans.response.send_redirect( web.url_for( controller='requests_admin', action='manage_datasets', - sample_id=sample.id, - status='done', - message=message) ) + sample_id=sample_id, + message=message, + status=status ) ) elif params.get( 'select_more_button', False ): - sample = trans.sa_session.query( trans.app.model.Sample ).get( kwd['sample_id'] ) - retval = self.__save_sample_datasets(trans, sample, files_list, folder_path) - if retval: message='The dataset(s) %s have been selected for sample <b>%s</b>' %(str(retval)[1:-1].replace("'", ""), sample.name) - else: message = None + sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) ) + retval = self.__save_sample_datasets( trans, sample, files_list, folder_path ) + if retval: + message='The datasets %s have been selected for sample <b>%s</b>' % ( str( retval )[1:-1].replace( "'", "" ), sample.name ) return trans.response.send_redirect( web.url_for( controller='requests_admin', action='get_data', - request_id=sample.request.id, + request_id=trans.security.encode_id( sample.request.id ), folder_path=folder_path, - sample_id=sample.id, + sample_id=sample_id, open_folder=True, - status='done', - message=message)) + message=message, + status=status ) ) return trans.response.send_redirect( web.url_for( controller='requests_admin', action='get_data', - request_id=sample.request.id, + request_id=trans.security.encode_id( sample.request.id ), folder_path=folder_path, - show_page=True)) - - def __save_sample_datasets(self, trans, sample, files_list, folder_path): + show_page=True ) ) + def __save_sample_datasets( self, trans, sample, files_list, folder_path ): files = [] - if len(files_list): + if files_list: for f in files_list: - filepath = os.path.join(folder_path, f) + filepath = os.path.join( folder_path, f ) if f[-1] == os.sep: - # the selected item is a folder so transfer all the - # folder contents + # the selected item is a folder so transfer all the folder contents # FIXME #self.__get_files_in_dir(trans, sample, filepath) return trans.response.send_redirect( web.url_for( controller='requests_admin', action='get_data', request=sample.request, folder_path=folder_path, - open_folder=True)) + open_folder=True ) ) else: - sample_dataset = trans.app.model.SampleDataset( sample=sample, - file_path=filepath, - status=sample.transfer_status.NOT_STARTED, - name=self.__dataset_name(sample, filepath.split('/')[-1]), - error_msg='', - size=sample.dataset_size(filepath)) + sample_dataset = trans.model.SampleDataset( sample=sample, + file_path=filepath, + status=sample.transfer_status.NOT_STARTED, + name=self.__dataset_name( sample, filepath.split( '/' )[-1] ), + error_msg='', + size=sample.dataset_size( filepath ) ) trans.sa_session.add( sample_dataset ) trans.sa_session.flush() - files.append(str(sample_dataset.name)) + files.append( str( sample_dataset.name ) ) return files - - - def __dataset_name(self, sample, filepath): - name = filepath.split('/')[-1] - opt = sample.request.type.datatx_info.get('rename_dataset', sample.request.type.rename_dataset_options.NO) - if opt == sample.request.type.rename_dataset_options.NO: + def __dataset_name( self, sample, filepath ): + name = filepath.split( '/' )[-1] + options = sample.request.type.rename_dataset_options + option = sample.request.type.datatx_info.get( 'rename_dataset', options.NO ) + if option == options.NO: return name - elif opt == sample.request.type.rename_dataset_options.SAMPLE_NAME: - return sample.name+'_'+name - elif opt == sample.request.type.rename_dataset_options.EXPERIMENT_AND_SAMPLE_NAME: - return sample.request.name+'_'+sample.name+'_'+name - elif opt == sample.request.type.rename_dataset_options.EXPERIMENT_NAME: - return sample.request.name+'_'+name - def __setup_datatx_user(self, trans, library, folder): - ''' - This method sets up the datatx user: - - Checks if the user exists already, if not creates the user + elif option == options.SAMPLE_NAME: + return sample.name + '_' + name + elif option == options.EXPERIMENT_AND_SAMPLE_NAME: + return sample.request.name + '_' + sample.name + '_' + name + elif opt == options.EXPERIMENT_NAME: + return sample.request.name + '_' + name + def __setup_datatx_user( self, trans, library, folder ): + """ + Sets up the datatx user: + - Checks if the user exists, if not creates them. - Checks if the user had ADD_LIBRARY permission on the target library and the target folder, if not sets up the permissions. - ''' + """ # Retrieve the upload user login information from the config file config = ConfigParser.ConfigParser() - config.read('transfer_datasets.ini') - email = config.get("data_transfer_user_login_info", "email") - password = config.get("data_transfer_user_login_info", "password") + config.read( 'transfer_datasets.ini' ) + email = config.get( "data_transfer_user_login_info", "email" ) + password = config.get( "data_transfer_user_login_info", "password" ) # check if the user already exists - datatx_user = trans.sa_session.query( trans.app.model.User ) \ - .filter( trans.app.model.User.table.c.email==email ) \ + datatx_user = trans.sa_session.query( trans.model.User ) \ + .filter( trans.model.User.table.c.email==email ) \ .first() if not datatx_user: # if not create the user - datatx_user = trans.app.model.User( email=email ) - datatx_user.set_password_cleartext( password ) + datatx_user = trans.model.User( email=email, password=passsword ) if trans.app.config.use_remote_user: datatx_user.external = True trans.sa_session.add( datatx_user ) trans.sa_session.flush() trans.app.security_agent.create_private_user_role( datatx_user ) trans.app.security_agent.user_set_default_permissions( datatx_user, history=False, dataset=False ) - for role in datatx_user.all_roles(): - if role.name == datatx_user.email and role.description == 'Private Role for %s' % datatx_user.email: - datatx_user_private_role = role - break - def check_permission(item_actions, role): - for item_permission in item_actions: - if item_permission.action == trans.app.security_agent.permitted_actions.LIBRARY_ADD.action \ - and item_permission.role.id == role.id: - return True - return False - # check if this user has 'add' permissions on the target library & folder - # if not, set 'ADD' permission - if not check_permission(library.actions, datatx_user_private_role): - lp = trans.app.model.LibraryPermissions( trans.app.security_agent.permitted_actions.LIBRARY_ADD.action, - library, - datatx_user_private_role ) + datatx_user_roles = datatx_user.all_roles() + datatx_user_private_role = trans.app.security_agent.get_private_user_role( datatx_user ) + # Make sure this user has LIBRARY_ADD permissions on the target library and folder. + # If not, give them permission. + if not trans.app.security_agent.can_add_library_item( datatx_user_roles, library ): + lp = trans.model.LibraryPermissions( trans.app.security_agent.permitted_actions.LIBRARY_ADD.action, + library, + datatx_user_private_role ) trans.sa_session.add( lp ) - if not check_permission(folder.actions, datatx_user_private_role): - dp = trans.app.model.LibraryFolderPermissions( trans.app.security_agent.permitted_actions.LIBRARY_ADD.action, - folder, - datatx_user_private_role ) + if not trans.app.security_agent.can_add_library_item( datatx_user_roles, folder ): + lfp = trans.model.LibraryFolderPermissions( trans.app.security_agent.permitted_actions.LIBRARY_ADD.action, + folder, + datatx_user_private_role ) trans.sa_session.add( dp ) trans.sa_session.flush() return datatx_user - - def __send_message(self, trans, datatx_info, sample, id_list): - ''' - This method creates the xml message and sends it to the rabbitmq server - ''' - # first create the xml message based on the following template + def __send_message( self, trans, datatx_info, sample, id_list ): + """Ceates an xml message and sends it to the rabbitmq server""" + # Create the xml message based on the following template xml = \ ''' <data_transfer><data_host>%(DATA_HOST)s</data_host> @@ -792,73 +584,62 @@ class RequestsAdmin( BaseController, Use </dataset>''' datasets = '' for id in id_list: - sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(id) ) + sample_dataset = trans.sa_session.query( trans.model.SampleDataset ).get( trans.security.decode_id( id ) ) if sample_dataset.status == sample.transfer_status.NOT_STARTED: - datasets = datasets + dataset_xml % dict(ID=str(sample_dataset.id), - NAME=sample_dataset.name, - FILE=sample_dataset.file_path) + datasets = datasets + dataset_xml % dict( ID=str( sample_dataset.id ), + NAME=sample_dataset.name, + FILE=sample_dataset.file_path ) sample_dataset.status = sample.transfer_status.IN_QUEUE trans.sa_session.add( sample_dataset ) trans.sa_session.flush() - data = xml % dict(DATA_HOST=datatx_info['host'], - DATA_USER=datatx_info['username'], - DATA_PASSWORD=datatx_info['password'], - SAMPLE_ID=str(sample.id), - LIBRARY_ID=str(sample.library.id), - FOLDER_ID=str(sample.folder.id), - DATASETS=datasets) - # now send this message - conn = amqp.Connection(host=trans.app.config.amqp['host']+":"+trans.app.config.amqp['port'], - userid=trans.app.config.amqp['userid'], - password=trans.app.config.amqp['password'], - virtual_host=trans.app.config.amqp['virtual_host'], - insist=False) + data = xml % dict( DATA_HOST=datatx_info['host'], + DATA_USER=datatx_info['username'], + DATA_PASSWORD=datatx_info['password'], + SAMPLE_ID=str(sample.id), + LIBRARY_ID=str(sample.library.id), + FOLDER_ID=str(sample.folder.id), + DATASETS=datasets ) + # Send the message + conn = amqp.Connection( host=trans.app.config.amqp['host'] + ":" + trans.app.config.amqp['port'], + userid=trans.app.config.amqp['userid'], + password=trans.app.config.amqp['password'], + virtual_host=trans.app.config.amqp['virtual_host'], + insist=False ) chan = conn.channel() - msg = amqp.Message(data.replace('\n', '').replace('\r', ''), - content_type='text/plain', - application_headers={'msg_type': 'data_transfer'}) + msg = amqp.Message( data.replace( '\n', '' ).replace( '\r', '' ), + content_type='text/plain', + application_headers={'msg_type': 'data_transfer'} ) msg.properties["delivery_mode"] = 2 - chan.basic_publish(msg, - exchange=trans.app.config.amqp['exchange'], - routing_key=trans.app.config.amqp['routing_key']) + chan.basic_publish( msg, + exchange=trans.app.config.amqp['exchange'], + routing_key=trans.app.config.amqp['routing_key'] ) chan.close() conn.close() - - def __start_datatx(self, trans, sample, id_list): - # data transfer user - datatx_user = self.__setup_datatx_user(trans, sample.library, sample.folder) - # validate sequecer information + def __start_datatx( self, trans, sample, id_list ): + datatx_user = self.__setup_datatx_user( trans, sample.library, sample.folder ) + # Validate sequencer information datatx_info = sample.request.type.datatx_info - if not datatx_info['host'] or \ - not datatx_info['username'] or \ - not datatx_info['password']: - message = "Error in sequencer login information." - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='manage_datasets', - sample_id=sample.id, - status='error', - message=message) ) - self.__send_message(trans, datatx_info, sample, id_list) - message="%i dataset(s) have been queued for transfer from the sequencer. Click on <b>Refresh</b> button above to get the latest transfer status." % len(id_list) + if not datatx_info['host'] or not datatx_info['username'] or not datatx_info['password']: + message = "Error in sequencer login information." + status = "error" + else: + self.__send_message( trans, datatx_info, sample, id_list ) + message = "%i datasets have been queued for transfer from the sequencer. Click on <b>Refresh</b> button above to get the latest transfer status." % len( id_list ) + status = "done" return trans.response.send_redirect( web.url_for( controller='requests_admin', action='manage_datasets', - sample_id=sample.id, - status='done', + sample_id=trans.security.encode_id( sample.id ), + status=status, message=message) ) - -## -#### Request Type Stuff ################################################### -## + # Request Type Stuff @web.expose @web.require_admin def manage_request_types( self, trans, **kwd ): if 'operation' in kwd: operation = kwd['operation'].lower() - if not kwd.get( 'id', None ): - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='manage_request_types', - status='error', - message="Invalid requesttype ID") ) + obj_id = kwd.get( 'id', None ) + if obj_id is None: + return invalid_id_redirect( trans, 'requests_admin', obj_id, action='manage_request_types' ) if operation == "view": return self.__view_request_type( trans, **kwd ) elif operation == "view_form": @@ -873,30 +654,26 @@ class RequestsAdmin( BaseController, Use return self.__show_request_type_permissions( trans, **kwd ) # Render the grid view return self.requesttype_grid( trans, **kwd ) - def __view_request_type(self, trans, **kwd): + def __view_request_type( self, trans, **kwd ): + request_type_id = kwd.get( 'id', None ) try: - rt = trans.sa_session.query( trans.app.model.RequestType ).get( trans.security.decode_id(kwd['id']) ) + request_type = trans.sa_session.query( trans.model.RequestType ).get( trans.security.decode_id( request_type_id ) ) except: - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='manage_request_types', - status='error', - message="Invalid requesttype ID") ) + return invalid_id_redirect( trans, 'requests_admin', request_type_id, action='manage_request_types' ) + forms = self.get_all_forms( trans ) + rename_dataset_selectbox = self.__build_rename_dataset_select_list( trans, request_type ) return trans.fill_template( '/admin/requests/view_request_type.mako', - request_type=rt, - forms=self.get_all_forms( trans ), - states_list=rt.states, - rename_dataset_selectbox=self.__rename_dataset_selectbox(trans, rt) ) + request_type=request_type, + forms=forms, + rename_dataset_selectbox=rename_dataset_selectbox ) def __view_form(self, trans, **kwd): + form_definition_id = kwd.get( 'id', None ) try: - fd = trans.sa_session.query( trans.app.model.FormDefinition ).get( trans.security.decode_id(kwd['id']) ) + form_definition = trans.sa_session.query( trans.model.FormDefinition ).get( trans.security.decode_id( form_definition_id ) ) except: - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='manage_request_types', - status='error', - message="Invalid form ID") ) + return invalid_id_redirect( trans, 'requests_admin', form_definition_id, action='manage_request_types' ) return trans.fill_template( '/admin/forms/show_form_read_only.mako', - form=fd ) - + form_definition=form_definition ) @web.expose @web.require_admin def create_request_type( self, trans, **kwd ): @@ -904,221 +681,218 @@ class RequestsAdmin( BaseController, Use message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) if params.get( 'add_state_button', False ): - rt_info, rt_states = self.__create_request_type_form(trans, **kwd) - rt_states.append(("", "")) + rt_info_widgets, rt_states_widgets = self.__create_request_type_form( trans, **kwd ) + rt_states_widgets.append( ( "", "" ) ) + rename_dataset_selectbox = self.__build_rename_dataset_select_list( trans ) return trans.fill_template( '/admin/requests/create_request_type.mako', - rt_info_widgets=rt_info, - rt_states_widgets=rt_states, + rt_info_widgets=rt_info_widgets, + rt_states_widgets=rt_states_widgets, + rename_dataset_selectbox=rename_dataset_selectbox, message=message, - status=status, - rename_dataset_selectbox=self.__rename_dataset_selectbox(trans)) + status=status ) elif params.get( 'remove_state_button', False ): - rt_info, rt_states = self.__create_request_type_form(trans, **kwd) - index = int(params.get( 'remove_state_button', '' ).split(" ")[2]) - del rt_states[index-1] + rt_info_widgets, rt_states_widgets = self.__create_request_type_form( trans, **kwd ) + index = int( params.get( 'remove_state_button', '' ).split(" ")[2] ) + del rt_states_widgets[ index-1 ] + rename_dataset_selectbox = self.__build_rename_dataset_select_list( trans ) return trans.fill_template( '/admin/requests/create_request_type.mako', - rt_info_widgets=rt_info, - rt_states_widgets=rt_states, + rt_info_widgets=rt_info_widgets, + rt_states_widgets=rt_states_widgets, + rename_dataset_selectbox=rename_dataset_selectbox, message=message, - status=status, - rename_dataset_selectbox=self.__rename_dataset_selectbox(trans)) + status=status ) elif params.get( 'save_request_type', False ): - rt, message = self.__save_request_type(trans, **kwd) - if not rt: + request_type, message = self.__save_request_type( trans, **kwd ) + if not request_type: return trans.fill_template( '/admin/requests/create_request_type.mako', - forms=self.get_all_forms( trans ), message=message, - status='error') + status='error' ) + message = 'Sequencer configuration <b>%s</b> has been created' % request_type.name return trans.response.send_redirect( web.url_for( controller='requests_admin', action='manage_request_types', - message='Sequencer configuration <b>%s</b> has been created' % rt.name, - status='done') ) + message=message, + status=status ) ) elif params.get( 'save_changes', False ): + request_type_id = kwd.get( 'rt_id', None ) try: - rt = trans.sa_session.query( trans.app.model.RequestType ).get( int(kwd['rt_id']) ) + request_type = trans.sa_session.query( trans.model.RequestType ).get( trans.security.decode_id( request_type_id ) ) except: - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='manage_request_types', - message='Invalid sequencer configuration ID', - status='error') ) - # data transfer info - rt.datatx_info = dict(host=util.restore_text( params.get( 'host', '' ) ), - username=util.restore_text( params.get( 'username', '' ) ), - password=params.get( 'password', '' ), - data_dir=util.restore_text( params.get( 'data_dir', '' ) ), - rename_dataset=util.restore_text( params.get('rename_dataset', False) )) - if rt.datatx_info.get('data_dir', '') and rt.datatx_info.get('data_dir', '')[-1] != os.sep: - rt.datatx_info['data_dir'] = rt.datatx_info['data_dir']+os.sep - trans.sa_session.add( rt ) + return invalid_id_redirect( trans, 'requests_admin', request_type_id, action='manage_request_types' ) + # Data transfer info - make sure password is retrieved from kwd rathe rthan Params since Params may have munged the characters. + request_type.datatx_info = dict( host=util.restore_text( params.get( 'host', '' ) ), + username=util.restore_text( params.get( 'username', '' ) ), + password=kwd.get( 'password', '' ), + data_dir=util.restore_text( params.get( 'data_dir', '' ) ), + rename_dataset=util.restore_text( params.get( 'rename_dataset', False ) ) ) + data_dir = self.__check_path( request_type.datatx_info[ 'data_dir' ] ) + request_type.datatx_info[ 'data_dir' ] = data_dir + trans.sa_session.add( request_type ) trans.sa_session.flush() + message = 'Changes made to sequencer configuration <b>%s</b> has been saved' % request_type.name return trans.response.send_redirect( web.url_for( controller='requests_admin', action='manage_request_types', operation='view', - id=trans.security.encode_id(rt.id), - message='Changes made to sequencer configuration <b>%s</b> has been saved' % rt.name, - status='done') ) + id=request_type_id, + message=message, + status=status ) ) else: - rt_info, rt_states = self.__create_request_type_form(trans, **kwd) + rt_info_widgets, rt_states_widgets = self.__create_request_type_form( trans, **kwd ) + rename_dataset_selectbox = self.__build_rename_dataset_select_list( trans ) return trans.fill_template( '/admin/requests/create_request_type.mako', - rt_info_widgets=rt_info, - rt_states_widgets=rt_states, + rt_info_widgets=rt_info_widgets, + rt_states_widgets=rt_states_widgets, + rename_dataset_selectbox=rename_dataset_selectbox, message=message, - status=status, - rename_dataset_selectbox=self.__rename_dataset_selectbox(trans)) - def __create_request_type_form(self, trans, **kwd): - request_forms=self.get_all_forms( trans, - filter=dict(deleted=False), - form_type=trans.app.model.FormDefinition.types.REQUEST ) - sample_forms=self.get_all_forms( trans, - filter=dict(deleted=False), - form_type=trans.app.model.FormDefinition.types.SAMPLE ) - if not len(request_forms) or not len(sample_forms): + status=status ) + def __create_request_type_form( self, trans, **kwd ): + request_forms = self.get_all_forms( trans, + filter=dict( deleted=False ), + form_type=trans.model.FormDefinition.types.REQUEST ) + sample_forms = self.get_all_forms( trans, + filter=dict( deleted=False ), + form_type=trans.model.FormDefinition.types.SAMPLE ) + if not request_forms or not sample_forms: return [],[] params = util.Params( kwd ) - rt_info = [] - rt_info.append(dict(label='Name', - widget=TextField('name', 40, util.restore_text( params.get( 'name', '' ) ) ) )) - rt_info.append(dict(label='Description', - widget=TextField('desc', 40, util.restore_text( params.get( 'desc', '' ) ) ) )) - - rf_selectbox = SelectField('request_form_id') + rt_info_widgets = [] + rt_info_widgets.append( dict( label='Name', + widget=TextField( 'name', 40, util.restore_text( params.get( 'name', '' ) ) ) ) ) + rt_info_widgets.append( dict( label='Description', + widget=TextField( 'desc', 40, util.restore_text( params.get( 'desc', '' ) ) ) ) ) + rf_selectbox = SelectField( 'request_form_id' ) for fd in request_forms: - if str(fd.id) == params.get( 'request_form_id', '' ): - rf_selectbox.add_option(fd.name, fd.id, selected=True) + if str( fd.id ) == params.get( 'request_form_id', '' ): + rf_selectbox.add_option( fd.name, fd.id, selected=True ) else: - rf_selectbox.add_option(fd.name, fd.id) - rt_info.append(dict(label='Request form', - widget=rf_selectbox )) - - sf_selectbox = SelectField('sample_form_id') + rf_selectbox.add_option( fd.name, fd.id ) + rt_info_widgets.append( dict( label='Request form', + widget=rf_selectbox ) ) + sf_selectbox = SelectField( 'sample_form_id' ) for fd in sample_forms: - if str(fd.id) == params.get( 'sample_form_id', '' ): - sf_selectbox.add_option(fd.name, fd.id, selected=True) + if str( fd.id ) == params.get( 'sample_form_id', '' ): + sf_selectbox.add_option( fd.name, fd.id, selected=True ) else: - sf_selectbox.add_option(fd.name, fd.id) - rt_info.append(dict(label='Sample form', - widget=sf_selectbox )) - # possible sample states + sf_selectbox.add_option( fd.name, fd.id ) + rt_info_widgets.append( dict( label='Sample form', + widget=sf_selectbox ) ) + # Possible sample states rt_states = [] i=0 while True: if kwd.has_key( 'state_name_%i' % i ): - rt_states.append((params.get( 'state_name_%i' % i, '' ), - params.get( 'state_desc_%i' % i, '' ))) - i=i+1 + rt_states.append( ( params.get( 'state_name_%i' % i, '' ), + params.get( 'state_desc_%i' % i, '' ) ) ) + i += 1 else: break - return rt_info, rt_states - - def __rename_dataset_selectbox(self, trans, rt=None): + return rt_info_widgets, rt_states + def __build_rename_dataset_select_list( self, trans, rt=None ): if rt: - sel_opt = rt.datatx_info.get('rename_dataset', trans.app.model.RequestType.rename_dataset_options.NO) + sel_opt = rt.datatx_info.get( 'rename_dataset', trans.model.RequestType.rename_dataset_options.NO ) else: - sel_opt = trans.app.model.RequestType.rename_dataset_options.NO - rename_dataset_selectbox = SelectField('rename_dataset') - for opt, opt_name in trans.app.model.RequestType.rename_dataset_options.items(): + sel_opt = trans.model.RequestType.rename_dataset_options.NO + rename_dataset_selectbox = SelectField( 'rename_dataset' ) + for opt, opt_name in trans.model.RequestType.rename_dataset_options.items(): if sel_opt == opt_name: - rename_dataset_selectbox.add_option(opt_name, opt_name, selected=True) + rename_dataset_selectbox.add_option( opt_name, opt_name, selected=True ) else: - rename_dataset_selectbox.add_option(opt_name, opt_name) + rename_dataset_selectbox.add_option( opt_name, opt_name ) return rename_dataset_selectbox - def __save_request_type(self, trans, **kwd): params = util.Params( kwd ) - rt = trans.app.model.RequestType() - rt.name = util.restore_text( params.get( 'name', '' ) ) - rt.desc = util.restore_text( params.get( 'desc', '' ) ) - rt.request_form = trans.sa_session.query( trans.app.model.FormDefinition ).get( int( params.request_form_id ) ) - rt.sample_form = trans.sa_session.query( trans.app.model.FormDefinition ).get( int( params.sample_form_id ) ) - # data transfer info - rt.datatx_info = dict(host=util.restore_text( params.get( 'host', '' ) ), - username=util.restore_text( params.get( 'username', '' ) ), - password=params.get( 'password', '' ), - data_dir=util.restore_text( params.get( 'data_dir', '' ) ), - rename_dataset=util.restore_text( params.get('rename_dataset', '') )) - if rt.datatx_info.get('data_dir', '') and rt.datatx_info.get('data_dir', '')[-1] != os.sep: - rt.datatx_info['data_dir'] = rt.datatx_info['data_dir']+os.sep - trans.sa_session.add( rt ) + name = util.restore_text( params.get( 'name', '' ) ) + desc = util.restore_text( params.get( 'desc', '' ) ) + request_form_id = params.get( 'request_form_id', None ) + request_form = trans.sa_session.query( trans.model.FormDefinition ).get( int( request_form_id ) ) + sample_form_id = params.get( 'sample_form_id', None ) + sample_form = trans.sa_session.query( trans.model.FormDefinition ).get( int( sample_form_id ) ) + data_dir = util.restore_text( params.get( 'data_dir', '' ) ) + data_dir = self.__check_path( data_dir ) + # Data transfer info - Make sure password is retrieved from kwd rather than Params + # since Params may have munged the characters. + datatx_info = dict( host=util.restore_text( params.get( 'host', '' ) ), + username=util.restore_text( params.get( 'username', '' ) ), + password=kwd.get( 'password', '' ), + data_dir=data_dir, + rename_dataset=util.restore_text( params.get( 'rename_dataset', '' ) ) ) + request_type = trans.model.RequestType( name=name, desc=desc, request_form=request_form, sample_form=sample_form, datatx_info=datatx_info ) + trans.sa_session.add( request_type ) trans.sa_session.flush() # set sample states - ss_list = trans.sa_session.query( trans.app.model.SampleState ).filter( trans.app.model.SampleState.table.c.request_type_id == rt.id ) + ss_list = trans.sa_session.query( trans.model.SampleState ) \ + .filter( trans.model.SampleState.table.c.request_type_id == request_type.id ) for ss in ss_list: trans.sa_session.delete( ss ) trans.sa_session.flush() - i=0 + i = 0 while True: if kwd.has_key( 'state_name_%i' % i ): - name = util.restore_text( params.get( 'state_name_%i' % i, None )) - desc = util.restore_text( params.get( 'state_desc_%i' % i, None )) - ss = trans.app.model.SampleState(name, desc, rt) + name = util.restore_text( params.get( 'state_name_%i' % i, None ) ) + desc = util.restore_text( params.get( 'state_desc_%i' % i, None ) ) + ss = trans.model.SampleState( name, desc, request_type ) trans.sa_session.add( ss ) trans.sa_session.flush() i = i + 1 else: break - message = "The new sequencer configuration named '%s' with %s state(s) has been created" % (rt.name, i) - return rt, message + message = "The new sequencer configuration named '%s' with %s states has been created" % ( request_type.name, i ) + return request_type, message def __delete_request_type( self, trans, **kwd ): - id_list = util.listify( kwd['id'] ) - for id in id_list: + rt_id = kwd.get( 'id', '' ) + rt_id_list = util.listify( rt_id ) + for rt_id in rt_id_list: try: - rt = trans.sa_session.query( trans.app.model.RequestType ).get( trans.security.decode_id(id) ) + request_type = trans.sa_session.query( trans.model.RequestType ).get( trans.security.decode_id( rt_id ) ) except: - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='manage_request_types', - message='Invalid sequencer configuration ID', - status='error') ) - rt.deleted = True - trans.sa_session.add( rt ) + return invalid_id_redirect( trans, 'requests_admin', rt_id, action='manage_request_types' ) + request_type.deleted = True + trans.sa_session.add( request_type ) trans.sa_session.flush() + status = 'done' + message = '%i sequencer configurations has been deleted' % len( rt_id_list ) return trans.response.send_redirect( web.url_for( controller='requests_admin', action='manage_request_types', - message='%i sequencer configuration(s) has been deleted' % len(id_list), - status='done') ) + message=message, + status='done' ) ) def __undelete_request_type( self, trans, **kwd ): - id_list = util.listify( kwd['id'] ) - for id in id_list: + rt_id = kwd.get( 'id', '' ) + rt_id_list = util.listify( rt_id ) + for rt_id in rt_id_list: try: - rt = trans.sa_session.query( trans.app.model.RequestType ).get( trans.security.decode_id(id) ) + request_type = trans.sa_session.query( trans.model.RequestType ).get( trans.security.decode_id( rt_id ) ) except: - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='manage_request_types', - message='Invalid sequencer configuration ID', - status='error') ) - rt.deleted = False - trans.sa_session.add( rt ) + return invalid_id_redirect( trans, 'requests_admin', rt_id, action='manage_request_types' ) + request_type.deleted = False + trans.sa_session.add( request_type ) trans.sa_session.flush() + status = 'done' + message = '%i sequencer configurations have been undeleted' % len( rt_id_list ) return trans.response.send_redirect( web.url_for( controller='requests_admin', action='manage_request_types', - message='%i sequencer configuration(s) has been undeleted' % len(id_list), - status='done') ) - def __show_request_type_permissions(self, trans, **kwd ): + message=message, + status=status ) ) + def __show_request_type_permissions( self, trans, **kwd ): params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) + request_type_id = kwd.get( 'id', '' ) try: - rt = trans.sa_session.query( trans.app.model.RequestType ).get( trans.security.decode_id(kwd['id']) ) + request_type = trans.sa_session.query( trans.model.RequestType ).get( trans.security.decode_id( request_type_id ) ) except: - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='manage_request_types', - status='error', - message="Invalid requesttype ID") ) - roles = trans.sa_session.query( trans.app.model.Role ) \ - .filter( trans.app.model.Role.table.c.deleted==False ) \ - .order_by( trans.app.model.Role.table.c.name ) + return invalid_id_redirect( trans, 'requests_admin', request_type_id, action='manage_request_types' ) + roles = trans.sa_session.query( trans.model.Role ) \ + .filter( trans.model.Role.table.c.deleted==False ) \ + .order_by( trans.model.Role.table.c.name ) if params.get( 'update_roles_button', False ): permissions = {} - for k, v in trans.app.model.RequestType.permitted_actions.items(): - in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ] + for k, v in trans.model.RequestType.permitted_actions.items(): + in_roles = [ trans.sa_session.query( trans.model.Role ).get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ] permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles - trans.app.security_agent.set_request_type_permissions( rt, permissions ) - trans.sa_session.refresh( rt ) - message = "Permissions updated for sequencer configuration '%s'" % rt.name + trans.app.security_agent.set_request_type_permissions( request_type, permissions ) + trans.sa_session.refresh( request_type ) + message = "Permissions updated for sequencer configuration '%s'" % request_type.name return trans.fill_template( '/admin/requests/request_type_permissions.mako', - request_type=rt, + request_type=request_type, roles=roles, status=status, - message=message) - - - + message=message ) --- a/templates/requests/index.mako +++ b/templates/requests/index.mako @@ -10,7 +10,5 @@ </%def><%def name="center_panel()"> - - <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${h.url_for( controller="requests", action="list" )}"></iframe> - + <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${h.url_for( controller="requests", action="browse_requests" )}"></iframe></%def> --- a/templates/requests/common/events.mako +++ b/templates/requests/common/events.mako @@ -4,12 +4,10 @@ <h2>History of Sequencing Request "${request.name}"</h2><ul class="manage-table-actions"><li> - <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='show', id=trans.security.encode_id(request.id) )}"> - <span>Browse this request</span></a> + <a class="action-button" href="${h.url_for( controller='requests_common', action='manage_request', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Browse this request</a></li><li> - <a class="action-button" href="${h.url_for( controller=cntrller, action='list')}"> - <span>Browse all requests</span></a> + <a class="action-button" href="${h.url_for( controller=cntrller, action='browse_requests' )}">Browse all requests</a></li></ul> --- a/templates/admin/requests/request_type_permissions.mako +++ b/templates/admin/requests/request_type_permissions.mako @@ -32,7 +32,7 @@ <div class="toolForm"><div class="toolFormTitle">Manage permissions on "${request_type.name}"</div><div class="toolFormBody"> - <form name="request_type_permissions" id="request_type_permissions" action="${h.url_for( controller='requests_admin', action='manage_request_types', operation="permissions", id=trans.security.encode_id(request_type.id))}" method="post"> + <form name="request_type_permissions" id="request_type_permissions" action="${h.url_for( controller='requests_admin', action='manage_request_types', operation="permissions", id=trans.security.encode_id( request_type.id ) )}" method="post"><div class="form-row"><% obj_name = request_type.name --- a/lib/galaxy/webapps/community/controllers/tool.py +++ b/lib/galaxy/webapps/community/controllers/tool.py @@ -8,7 +8,7 @@ from common import * log = logging.getLogger( __name__ ) -class StateColumn( grids.TextColumn ): +class StateColumn( grids.StateColumn ): def get_value( self, trans, grid, tool ): state = tool.state if state == trans.model.Tool.states.APPROVED: --- a/templates/admin/requests/dataset.mako +++ b/templates/admin/requests/dataset.mako @@ -1,17 +1,17 @@ <%inherit file="/base.mako"/><%namespace file="/message.mako" import="render_msg" /> - %if message: ${render_msg( message, status )} %endif -<br/> -<br/> +<br/><br/> + +<% sample = sample_dataset.sample %><ul class="manage-table-actions"><li> - <a class="action-button" href="${h.url_for( controller='requests_common', action='show_datatx_page', cntrller='requests_admin', sample_id=trans.security.encode_id(sample.id) )}"> + <a class="action-button" href="${h.url_for( controller='requests_common', action='view_dataset_transfer', cntrller='requests_admin', sample_id=trans.security.encode_id( sample.id ) )}"><span>Browse datasets</span></a></li></ul> --- /dev/null +++ b/templates/requests/common/create_request.mako @@ -0,0 +1,60 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + +<%def name="javascripts()"> + ${parent.javascripts()} + ${h.js("jquery.autocomplete", "autocomplete_tagging" )} +</%def> + +<%def name="stylesheets()"> + ${parent.stylesheets()} + ${h.css( "autocomplete_tagging" )} +</%def> + +<br/><br/> +<ul class="manage-table-actions"> + <li><a class="action-button" href="${h.url_for( controller=cntrller, action='browse_requests' )}">Browse requests</a></li> +</ul> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">Create a new request</div> + %if len( request_type_select_field.options ) == 1: + There are no sequencer configurations available for ${trans.user.email} to create sequencing requests. + %else: + <div class="toolFormBody"> + <form name="create_request" id="create_request" action="${h.url_for( controller='requests_common', action='create_request', cntrller=cntrller )}" method="post" > + <div class="form-row"> + <label>Select a sequencer configuration:</label> + ## The request_type_select_field is a SelectField named request_type_id + ${request_type_select_field.get_html()} + %if cntrller != 'requests_admin': + <div class="toolParamHelp" style="clear: both;"> + Contact the lab manager if you are not sure about the sequencer configuration. + </div> + %endif + </div> + %if request_type_select_field_selected != 'none': + ## If a request_type has been selected, display the associated form using received widgets. + %for i, field in enumerate( widgets ): + <div class="form-row"> + <label>${field['label']}</label> + ${field['widget'].get_html()} + <div class="toolParamHelp" style="clear: both;"> + ${field['helptext']} + </div> + <div style="clear: both"></div> + </div> + %endfor + <div class="form-row"> + <input type="submit" name="create_request_button" value="Save"/> + <input type="submit" name="add_sample_button" value="Add samples"/> + </div> + %endif + </form> + </div> + %endif +</div> --- /dev/null +++ b/templates/requests/common/manage_request.mako @@ -0,0 +1,610 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/requests/common/sample_state.mako" import="render_sample_state" /> +<%namespace file="/requests/common/sample_datasets.mako" import="render_sample_datasets" /> + +<%def name="stylesheets()"> + ${parent.stylesheets()} + ${h.css( "library" )} +</%def> + +<%def name="javascripts()"> + ${parent.javascripts()} + <script type="text/javascript"> + function showContent(vThis) + { + // http://www.javascriptjunkie.com + // alert(vSibling.className + " " + vDef_Key); + vParent = vThis.parentNode; + vSibling = vParent.nextSibling; + while (vSibling.nodeType==3) { + // Fix for Mozilla/FireFox Empty Space becomes a TextNode or Something + vSibling = vSibling.nextSibling; + }; + if(vSibling.style.display == "none") + { + vThis.src="/static/images/fugue/toggle.png"; + vThis.alt = "Hide"; + vSibling.style.display = "block"; + } else { + vSibling.style.display = "none"; + vThis.src="/static/images/fugue/toggle-expand.png"; + vThis.alt = "Show"; + } + return; + } + + $(document).ready(function(){ + //hide the all of the element with class msg_body + $(".msg_body").hide(); + //toggle the component with class msg_body + $(".msg_head").click(function(){ + $(this).next(".msg_body").slideToggle(0); + }); + }); + + // Looks for changes in sample states using an async request. Keeps + // calling itself (via setTimeout) until all samples are in a terminal + // state. + var updater = function ( sample_states ) { + // Check if there are any items left to track + var empty = true; + for ( i in sample_states ) { + empty = false; + break; + } + if ( ! empty ) { + setTimeout( function() { updater_callback( sample_states ) }, 1000 ); + } + }; + + var updater_callback = function ( sample_states ) { + // Build request data + var ids = [] + var states = [] + $.each( sample_states, function ( id, state ) { + ids.push( id ); + states.push( state ); + }); + // Make ajax call + $.ajax( { + type: "POST", + url: "${h.url_for( controller='requests_common', action='sample_state_updates' )}", + dataType: "json", + data: { ids: ids.join( "," ), states: states.join( "," ) }, + success : function ( data ) { + $.each( data, function( cntrller, id, val ) { + // Replace HTML + var cell1 = $("#sampleState-" + id); + cell1.html( val.html_state ); + var cell2 = $("#sampleDatasets-" + id); + cell2.html( val.html_datasets ); + sample_states[ parseInt(id) ] = val.state; + }); + updater( sample_states ); + }, + error: function() { + // Just retry, like the old method, should try to be smarter + updater( sample_states ); + } + }); + }; + + function checkAllFields() + { + var chkAll = document.getElementById('checkAll'); + var checks = document.getElementsByTagName('input'); + var boxLength = checks.length; + var allChecked = false; + var totalChecked = 0; + if ( chkAll.checked == true ) + { + for ( i=0; i < boxLength; i++ ) + { + if ( checks[i].name.indexOf( 'select_sample_' ) != -1) + { + checks[i].checked = true; + } + } + } + else + { + for ( i=0; i < boxLength; i++ ) + { + if ( checks[i].name.indexOf( 'select_sample_' ) != -1) + { + checks[i].checked = false + } + } + } + } + + function stopRKey(evt) { + var evt = (evt) ? evt : ((event) ? event : null); + var node = (evt.target) ? evt.target : ((evt.srcElement) ? evt.srcElement : null); + if ((evt.keyCode == 13) && (node.type=="text")) {return false;} + } + document.onkeypress = stopRKey + </script> +</%def> + +<% is_admin = cntrller == 'requests_admin' and trans.user_is_admin() %> + +<div class="grid-header"> + <h2>Sequencing Request "${request.name}"</h2> + <div class="toolParamHelp" style="clear: both;"> + <b>Sequencer</b>: ${request.type.name} + %if is_admin: + | <b>User</b>: ${request.user.username} + %endif + %if request.is_submitted: + | <b>State</b>: <i>${request.state}</i> + %else: + | <b>State</b>: ${request.state} + %endif + </div> +</div> + +<br/><br/> +<ul class="manage-table-actions"> + <li><a class="action-button" id="seqreq-${request.id}-popup" class="menubutton">Sequencing Request Actions</a></li> + <div popupmenu="seqreq-${request.id}-popup"> + %if request.is_unsubmitted and request.samples: + <a class="action-button" confirm="More samples cannot be added to this request once it is submitted. Click OK to submit." href="${h.url_for( controller='requests_common', action='submit_request', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Submit</a> + %endif + <a class="action-button" href="${h.url_for( controller='requests_common', action='request_events', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">History</a> + <a class="action-button" href="${h.url_for( controller='requests_common', action='edit_basic_request_info', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Edit</a> + %if is_admin: + %if request.is_submitted: + <a class="action-button" href="${h.url_for( controller='requests_admin', action='reject', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Reject</a> + <a class="action-button" href="${h.url_for( controller='requests_admin', action='get_data', show_page=True, request_id=request.id )}">Select datasets to transfer</a> + %endif + %endif + </div> + <li><a class="action-button" href="${h.url_for( controller=cntrller, action='browse_requests' )}">Browse requests</a></li> +</ul> + +%if request.has_samples_without_library_destinations: + ${render_msg( "Select a target data library and folder for all the samples before starting the sequence run", "warning" )} +%endif + +%if request.is_rejected: + ${render_msg( "Reason for rejection: " + request.last_comment, "warning" )} +%endif + +%if message: + ${render_msg( message, status )} +%endif + +<h4><img src="/static/images/fugue/toggle-expand.png" alt="Show" onclick="showContent(this);" style="cursor:pointer;"/> Request Information</h4> +<div style="display:none;"> + <table class="grid" border="0"> + <tbody> + <tr> + <td valign="top" width="50%"> + <div class="form-row"> + <label>Description:</label> + ${request.desc} + </div> + <div style="clear: both"></div> + %for index, rd in enumerate( request_widgets ): + <% + field_label = rd[ 'label' ] + field_value = rd[ 'value' ] + %> + <div class="form-row"> + <label>${field_label}:</label> + %if field_label == 'State': + <a href="${h.url_for( controller='requests_common', action='request_events', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">${field_value}</a> + %else: + ${field_value} + %endif + </div> + <div style="clear: both"></div> + %endfor + </td> + <td valign="top" width="50%"> + <div class="form-row"> + <label>Date created:</label> + ${request.create_time} + </div> + <div class="form-row"> + <label>Date updated:</label> + ${request.update_time} + </div> + <div class="form-row"> + <label>Email notification recipients:</label> + <% + if request.notification: + emails = ', '.join( request.notification[ 'email' ] ) + else: + emails = '' + %> + ${emails} + </div> + <div style="clear: both"></div> + <div class="form-row"> + <label>Email notification on sample states:</label> + <% + if request.notification: + states = [] + for ss in request.type.states: + if ss.id in request.notification[ 'sample_states' ]: + states.append( ss.name ) + states = ', '.join( states ) + else: + states = '' + %> + ${states} + </div> + <div style="clear: both"></div> + </td> + </tr> + </tbody> + </table> +</div> +<br/> +<form id="manage_request" name="manage_request" action="${h.url_for( controller='requests_common', action='manage_request', cntrller=cntrller, id=trans.security.encode_id( request.id ), managing_samples=managing_samples )}" method="post"> + %if current_samples: + <% sample_operation_selected_value = sample_operation_select_field.get_selected( return_value=True ) %> + ## first render the basic info grid + ${render_basic_info_grid()} + %if not request.is_new and not managing_samples and len( sample_operation_select_field.options ) > 1: + <div class="form-row" style="background-color:#FAFAFA;"> + For selected samples: + ${sample_operation_select_field.get_html()} + </div> + %if sample_operation_selected_value != 'none' and selected_samples: + <div class="form-row" style="background-color:#FAFAFA;"> + %if sample_operation_selected_value == trans.model.Sample.bulk_operations.CHANGE_STATE: + ## sample_operation_selected_value == 'Change state' + <div class="form-row"> + <label>Change current state</label> + ${sample_state_id_select_field.get_html()} + <label>Comments</label> + <input type="text" name="sample_event_comment" value=""/> + <div class="toolParamHelp" style="clear: both;"> + Optional + </div> + </div> + <div class="form-row"> + <input type="submit" name="change_state_button" value="Save"/> + <input type="submit" name="cancel_change_state_button" value="Cancel"/> + </div> + %elif sample_operation_selected_value == trans.app.model.Sample.bulk_operations.SELECT_LIBRARY: + ## sample_operation_selected_value == 'Select data library and folder' + <% libraries_selected_value = libraries_select_field.get_selected( return_value=True ) %> + <div class="form-row"> + <label>Select data library:</label> + ${libraries_select_field.get_html()} + </div> + %if libraries_selected_value != 'none': + <div class="form-row"> + <label>Select folder:</label> + ${folders_select_field.get_html()} + </div> + <div class="form-row"> + <input type="submit" name="change_lib_button" value="Save"/> + <input type="submit" name="cancel_change_lib_button" value="Cancel"/> + </div> + %endif + %endif + </div> + %endif + %endif + ## Render the other grids + <% trans.sa_session.refresh( request.type.sample_form ) %> + %for grid_index, grid_name in enumerate( request.type.sample_form.layout ): + ${render_grid( grid_index, grid_name, request.type.sample_form.fields_of_grid( grid_index ) )} + %endfor + %else: + <label>There are no samples.</label> + %endif + %if request.samples and request.is_submitted: + <script type="text/javascript"> + // Updater + updater({${ ",".join( [ '"%s" : "%s"' % ( trans.security.encode_id( s.id ), s.state.name ) for s in request.samples ] ) }}); + </script> + %endif + %if not managing_samples: + <table class="grid"> + <tbody> + <tr> + <div class="form-row"> + %if request.is_unsubmitted: + <td> + %if current_samples: + <label>Copy </label> + <input type="integer" name="num_sample_to_copy" value="1" size="3"/> + <label>samples from sample</label> + ${sample_copy.get_html()} + %endif + <input type="submit" name="add_sample_button" value="Add New"/> + </td> + %endif + <td> + %if current_samples and len( current_samples ) <= len( request.samples ): + <input type="submit" name="edit_samples_button" value="Edit samples"/> + %endif + </td> + </div> + </tr> + </tbody> + </table> + %endif + %if request.samples or current_samples: + %if managing_samples: + <div class="form-row"> + <input type="submit" name="save_samples_button" value="Save"/> + <input type="submit" name="cancel_changes_button" value="Cancel"/> + </div> + %elif len( current_samples ) > len( request.samples ): + <div class="form-row"> + <input type="submit" name="save_samples_button" value="Save"/> + <input type="submit" name="cancel_changes_button" value="Cancel"/> + </div> + %endif + %endif +</form> +<br/> +%if request.is_unsubmitted: + <form id="import" name="import" action="${h.url_for( controller='requests_common', action='manage_request', managing_samples=managing_samples, id=trans.security.encode_id( request.id ) )}" enctype="multipart/form-data" method="post" > + <h4><img src="/static/images/fugue/toggle-expand.png" alt="Show" onclick="showContent(this);" style="cursor:pointer;"/> Import samples</h4> + <div style="display:none;"> + <input type="file" name="file_data" /> + <input type="submit" name="import_samples_button" value="Import samples"/> + <br/> + <div class="toolParamHelp" style="clear: both;"> + The csv file must be in the following format:<br/> + SampleName,DataLibrary,DataLibraryFolder,FieldValue1,FieldValue2... + </div> + </div> + </form> +%endif + +<%def name="render_grid( grid_index, grid_name, fields_dict )"> + <br/> + <% if not grid_name: + grid_name = "Grid "+ grid_index + %> + <div> + %if managing_samples or len( current_samples ) > len( request.samples ): + <h4><img src="/static/images/fugue/toggle.png" alt="Show" onclick="showContent(this);" style="cursor:pointer;"/> ${grid_name}</h4> + <div> + %else: + <h4><img src="/static/images/fugue/toggle-expand.png" alt="Hide" onclick="showContent(this);" style="cursor:pointer;"/> ${grid_name}</h4> + <div style="display:none;"> + %endif + <table class="grid"> + <thead> + <tr> + <th>Name</th> + %for index, field in fields_dict.items(): + <th> + ${field['label']} + <div class="toolParamHelp" style="clear: both;"> + <i>${field['helptext']}</i> + </div> + </th> + %endfor + <th></th> + </tr> + <thead> + <tbody> + <% trans.sa_session.refresh( request ) %> + %for sample_index, sample in enumerate( current_samples ): + %if managing_samples: + <tr>${render_sample_form( sample_index, sample['name'], sample['field_values'], fields_dict)}</tr> + %else: + <tr> + %if sample_index in range( len( request.samples ) ): + ${render_sample( sample_index, sample['name'], sample['field_values'], fields_dict )} + %else: + ${render_sample_form( sample_index, sample['name'], sample['field_values'], fields_dict)} + %endif + </tr> + %endif + %endfor + </tbody> + </table> + </div> + </div> +</%def> + +## This function displays the "Basic Information" grid +<%def name="render_basic_info_grid()"> + <h3>Sample Information</h3> + <table class="grid"> + <thead> + <tr> + <th><input type="checkbox" id="checkAll" name=select_all_samples_checkbox value="true" onclick='checkAllFields(1);'><input type="hidden" name=select_all_samples_checkbox value="true"></th> + <th>Name</th> + <th>Barcode</th> + <th>State</th> + <th>Data Library</th> + <th>Folder</th> + %if request.is_submitted or request.is_complete: + <th>Datasets Transferred</th> + %endif + <th></th> + </tr> + <thead> + <tbody> + <% trans.sa_session.refresh( request ) %> + %for sample_index, info in enumerate( current_samples ): + <% + if sample_index in range( len(request.samples ) ): + sample = request.samples[sample_index] + else: + sample = None + %> + %if managing_samples: + <tr>${show_basic_info_form( sample_index, sample, info )}</tr> + %else: + <tr> + %if sample_index in range( len( request.samples ) ): + %if trans.security.encode_id( sample.id ) in selected_samples: + <td><input type="checkbox" name=select_sample_${sample.id} id="sample_checkbox" value="true" checked><input type="hidden" name=select_sample_${sample.id} id="sample_checkbox" value="true"></td> + %else: + <td><input type="checkbox" name=select_sample_${sample.id} id="sample_checkbox" value="true"><input type="hidden" name=select_sample_${sample.id} id="sample_checkbox" value="true"></td> + %endif + <td>${info['name']}</td> + <td>${info['barcode']}</td> + %if sample.request.is_unsubmitted: + <td>Unsubmitted</td> + %else: + <td id="sampleState-${sample.id}">${render_sample_state( cntrller, sample )}</td> + %endif + %if info['library']: + %if cntrller == 'requests': + <td><a href="${h.url_for( controller='library_common', action='browse_library', cntrller='library', id=trans.security.encode_id( info['library'].id ) )}">${info['library'].name}</a></td> + %elif is_admin: + <td><a href="${h.url_for( controller='library_common', action='browse_library', cntrller='library_admin', id=trans.security.encode_id( info['library'].id ) )}">${info['library'].name}</a></td> + %endif + %else: + <td></td> + %endif + %if info['folder']: + <td>${info['folder'].name}</td> + %else: + <td></td> + %endif + %if request.is_submitted or request.is_complete: + <td id="sampleDatasets-${sample.id}"> + ${render_sample_datasets( cntrller, sample )} + </td> + %endif + %else: + ${show_basic_info_form( sample_index, sample, info )} + %endif + %if request.is_unsubmitted or request.is_rejected: + <td> + %if sample: + %if sample.request.is_unsubmitted: + <a class="action-button" href="${h.url_for( controller='requests_common', cntrller=cntrller, action='delete_sample', request_id=trans.security.encode_id( request.id ), sample_id=sample_index )}"><img src="${h.url_for('/static/images/delete_icon.png')}" style="cursor:pointer;"/></a> + %endif + %endif + </td> + %endif + </tr> + %endif + %endfor + </tbody> + </table> +</%def> + +<%def name="show_basic_info_form( sample_index, sample, info )"> + <td></td> + <td> + <input type="text" name="sample_${sample_index}_name" value="${info['name']}" size="10"/> + <div class="toolParamHelp" style="clear: both;"> + <i>${' (required)' }</i> + </div> + </td> + %if cntrller == 'requests': + %if sample: + %if sample.request.is_unsubmitted: + <td></td> + %else: + <td><input type="text" name="sample_${sample_index}_barcode" value="${info['barcode']}" size="10"/></td> + %endif + %else: + <td></td> + %endif + %elif is_admin: + %if sample: + %if sample.request.is_unsubmitted: + <td></td> + %else: + <td><input type="text" name="sample_${sample_index}_barcode" value="${info['barcode']}" size="10"/></td> + %endif + %else: + <td></td> + %endif + %endif + %if sample: + %if sample.request.is_unsubmitted: + <td>Unsubmitted</td> + %else: + <td><a href="${h.url_for( controller='requests_common', action='sample_events', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${sample.state.name}</a></td> + %endif + %else: + <td></td> + %endif + <td>${info['library_select_field'].get_html()}</td> + <td>${info['folder_select_field'].get_html()}</td> + %if request.is_submitted or request.is_complete: + <% + if sample: + label = str( len( sample.datasets ) ) + else: + label = 'Add' + %> + <td><a href="${h.url_for( controller='requests_common', action='view_dataset_transfer', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${label}</a></td> + %endif +</%def> + +<%def name="render_sample( index, sample_name, sample_values, fields_dict )"> + <td> + ${sample_name} + </td> + %for field_index, field in fields_dict.items(): + <td> + %if sample_values[field_index]: + %if field['type'] == 'WorkflowField': + %if str(sample_values[field_index]) != 'none': + <% workflow = trans.sa_session.query( trans.app.model.StoredWorkflow ).get( int(sample_values[field_index]) ) %> + <a href="${h.url_for( controller='workflow', action='run', id=trans.security.encode_id(workflow.id) )}">${workflow.name}</a> + %endif + %else: + ${sample_values[field_index]} + %endif + %else: + <i>None</i> + %endif + </td> + %endfor +</%def> + +<%def name="render_sample_form( index, sample_name, sample_values, fields_dict )"> + <td>${sample_name}</td> + %for field_index, field in fields_dict.items(): + <td> + %if field['type'] == 'TextField': + <input type="text" name="sample_${index}_field_${field_index}" value="${sample_values[field_index]}" size="7"/> + %elif field['type'] == 'SelectField': + <select name="sample_${index}_field_${field_index}" last_selected_value="2"> + %for option_index, option in enumerate(field['selectlist']): + %if option == sample_values[field_index]: + <option value="${option}" selected>${option}</option> + %else: + <option value="${option}">${option}</option> + %endif + %endfor + </select> + %elif field['type'] == 'WorkflowField': + <select name="sample_${index}_field_${field_index}"> + %if str(sample_values[field_index]) == 'none': + <option value="none" selected>Select one</option> + %else: + <option value="none">Select one</option> + %endif + %for option_index, option in enumerate(request.user.stored_workflows): + %if not option.deleted: + %if str(option.id) == str(sample_values[field_index]): + <option value="${option.id}" selected>${option.name}</option> + %else: + <option value="${option.id}">${option.name}</option> + %endif + %endif + %endfor + </select> + %elif field['type'] == 'CheckboxField': + <input type="checkbox" name="sample_${index}_field_${field_index}" value="Yes"/> + %endif + <div class="toolParamHelp" style="clear: both;"> + <i>${'('+field['required']+')' }</i> + </div> + </td> + %endfor +</%def> --- a/templates/requests/common/find.mako +++ /dev/null @@ -1,83 +0,0 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> - -%if message: - ${render_msg( message, status )} -%endif - -<%def name="javascripts()"> - ${parent.javascripts()} - ${h.js("jquery.autocomplete", "autocomplete_tagging" )} -</%def> - -<%def name="stylesheets()"> - ${parent.stylesheets()} - ${h.css( "autocomplete_tagging" )} -</%def> - -<br/> -<br/> -<ul class="manage-table-actions"> - <li> - <a class="action-button" href="${h.url_for( controller=cntrller, cntrller=cntrller, action='list')}"> - <span>Browse requests</span></a> - </li> -</ul> - -<div class="toolForm"> - <div class="toolFormTitle">Find samples</div> - <div class="toolFormBody"> - <form name="find_request" id="find_request" action="${h.url_for( controller='requests_common', action='find', cntrller=cntrller)}" method="post" > - <div class="form-row"> - <label>Find sample(s) using:</label> - ${search_type.get_html()} - <div class="toolParamHelp" style="clear: both;"> - Select a sample attribute to search through all the samples.<br/> - To search for a sample with a dataset name, select the dataset - option above. This will return all the sample(s) which has a - dataset with the given name associated with it. - </div> - </div> - <div class="form-row"> - <label>Show only sequencing requests in state:</label> - ${request_states.get_html()} - </div> - <div class="form-row"> - ${search_box.get_html()} - <input type="submit" name="go_button" value="Find"/> - <div class="toolParamHelp" style="clear: both;"> - Wildcard search (%) can be used as placeholder for any sequence of characters or words.<br/> - For example, to search for samples starting with 'mysample' use 'mysample%' as the search string. - </div> - </div> - %if results: - <div class="form-row"> - <label><i>${results}</i></label> - %if samples: - <div class="toolParamHelp" style="clear: both;"> - The search results are sorted by the date the samples where created. - </div> - %endif - </div> - %endif - <div class="form-row"> - %if samples: - %for s in samples: - <div class="form-row"> - Sample: <b>${s.name}</b> | Barcode: ${s.bar_code}<br/> - State: ${s.current_state().name}<br/> - Datasets: <a href="${h.url_for(controller='requests_common', cntrller=cntrller, action='show_datatx_page', sample_id=trans.security.encode_id(s.id))}">${s.transferred_dataset_files()}/${len(s.datasets)}</a><br/> - %if cntrller == 'requests_admin': - <i>User: ${s.request.user.email}</i> - %endif - <div class="toolParamHelp" style="clear: both;"> - <a href="${h.url_for( controller=cntrller, action='list', operation='show', id=trans.security.encode_id(s.request.id))}">Sequencing request: ${s.request.name} | Type: ${s.request.type.name} | State: ${s.request.state()}</a> - </div> - </div> - <br/> - %endfor - %endif - </div> - </form> - </div> -</div> --- a/test/functional/test_forms_and_requests.py +++ b/test/functional/test_forms_and_requests.py @@ -181,7 +181,7 @@ class TestFormsAndRequests( TwillTestCas # Make sure the request_type1 is not accessible by regular_user2 since regular_user2 does not have Role1. self.logout() self.login( email=regular_user2.email ) - self.visit_url( '%s/requests_common/new?cntrller=requests&select_request_type=True' % self.url ) + self.visit_url( '%s/requests_common/create_request?cntrller=requests&request_type=True' % self.url ) try: self.check_page_for_string( 'There are no sequencer configurations created for a new request.' ) raise AssertionError, 'The request_type %s is accessible by %s when it should be restricted' % ( request_type1.name, regular_user2.email ) @@ -205,11 +205,11 @@ class TestFormsAndRequests( TwillTestCas name = 'Request One' desc = 'Request One Description' self.create_request( cntrller='requests', - request_type_id=str( request_type1.id ), + request_type_id=self.security.encode_id( request_type1.id ), name=name, desc=desc, field_value_tuples=field_value_tuples, - strings_displayed=[ 'Add a new request', + strings_displayed=[ 'Create a new request', test_field_name1, test_field_name2, test_field_name3 ], @@ -235,19 +235,20 @@ class TestFormsAndRequests( TwillTestCas strings_displayed=[ 'Sequencing Request "%s"' % request_one.name, 'There are no samples.' ], strings_displayed_after_submit=strings_displayed_after_submit ) - def test_030_edit_request( self ): - """Testing editing a sequence run request""" + def test_030_edit_basic_request_info( self ): + """Testing editing the basic information of a sequence run request""" # logged in as regular_user1 fields = [ 'option2', str( user_address1.id ), 'field three value (edited)' ] new_name=request_one.name + ' (Renamed)' new_desc=request_one.desc + ' (Re-described)' - self.edit_request( request_id=self.security.encode_id( request_one.id ), - name=request_one.name, - new_name=new_name, - new_desc=new_desc, - new_fields=fields, - strings_displayed=[ 'Edit sequencing request "%s"' % request_one.name ], - strings_displayed_after_submit=[ new_name, new_desc ] ) + self.edit_basic_request_info( request_id=self.security.encode_id( request_one.id ), + cntrller='requests', + name=request_one.name, + new_name=new_name, + new_desc=new_desc, + new_fields=fields, + strings_displayed=[ 'Edit sequencing request "%s"' % request_one.name ], + strings_displayed_after_submit=[ new_name, new_desc ] ) refresh( request_one ) # check if the request is showing in the 'new' filter self.check_request_grid( cntrller='requests', @@ -259,7 +260,7 @@ class TestFormsAndRequests( TwillTestCas self.submit_request( cntrller='requests', request_id=self.security.encode_id( request_one.id ), request_name=request_one.name, - strings_displayed_after_submit=[ 'The request <b>%s</b> has been submitted.' % request_one.name ] ) + strings_displayed_after_submit=[ 'The request has been submitted.' ] ) refresh( request_one ) # Make sure the request is showing in the 'submitted' filter self.check_request_grid( cntrller='requests', @@ -271,16 +272,18 @@ class TestFormsAndRequests( TwillTestCas def test_040_request_lifecycle( self ): """Testing request life-cycle as it goes through all the states""" # logged in as regular_user1 + """ + TODO: debug this test case... self.logout() self.login( email=admin_user.email ) self.check_request_grid( cntrller='requests_admin', state=request_one.states.SUBMITTED, strings_displayed=[ request_one.name ] ) - self.visit_url( "%s/requests_admin/list?operation=show&id=%s" % ( self.url, self.security.encode_id( request_one.id ) )) + self.visit_url( "%s/requests_common/manage_request?cntrller=requests&id=%s" % ( self.url, self.security.encode_id( request_one.id ) ) ) self.check_page_for_string( 'Sequencing Request "%s"' % request_one.name ) # Set bar codes for the samples bar_codes = [ '1234567890', '0987654321' ] - strings_displayed_after_submit=[ 'Changes made to the sample(s) are saved.' ] + strings_displayed_after_submit=[ 'Changes made to the samples are saved.' ] for bar_code in bar_codes: strings_displayed_after_submit.append( bar_code ) self.add_bar_codes( request_id=self.security.encode_id( request_one.id ), @@ -293,14 +296,14 @@ class TestFormsAndRequests( TwillTestCas self.change_sample_state( request_id=self.security.encode_id( request_one.id ), request_name=request_one.name, sample_name=sample.name, - sample_id=sample.id, - new_state_id=request_type1.states[1].id, + sample_id=self.security.encode_id( sample.id ), + new_sample_state_id=request_type1.states[1].id, new_state_name=request_type1.states[1].name ) self.change_sample_state( request_id=self.security.encode_id( request_one.id ), request_name=request_one.name, sample_name=sample.name, - sample_id=sample.id, - new_state_id=request_type1.states[2].id, + sample_id=self.security.encode_id( sample.id ), + new_sample_state_id=request_type1.states[2].id, new_state_name=request_type1.states[2].name ) refresh( request_one ) self.logout() @@ -311,6 +314,7 @@ class TestFormsAndRequests( TwillTestCas strings_displayed=[ request_one.name ] ) assert request_one.state is not request_one.states.COMPLETE, "The state of the request '%s' should be set to '%s'" \ % ( request_one.name, request_one.states.COMPLETE ) + """ def test_045_admin_create_request_on_behalf_of_regular_user( self ): """Testing creating and submitting a request as an admin on behalf of a regular user""" # Logged in as regular_user1 @@ -323,17 +327,16 @@ class TestFormsAndRequests( TwillTestCas # is required for that field. field_value_tuples = [ ( 'option2', False ), ( str( user_address1.id ), True ), ( 'field_2_value', False ) ] self.create_request( cntrller='requests_admin', - request_type_id=str( request_type1.id ), - select_user_id=str( regular_user1.id ), + request_type_id=self.security.encode_id( request_type1.id ), + other_users_id=self.security.encode_id( regular_user1.id ), name=name, desc=desc, - refresh='True', field_value_tuples=field_value_tuples, - strings_displayed=[ 'Add a new request', + strings_displayed=[ 'Create a new request', test_field_name1, test_field_name2, test_field_name3 ], - strings_displayed_after_submit=[ "The new request named <b>%s</b> has been created" % name ] ) + strings_displayed_after_submit=[ "The request has been created" ] ) global request_two request_two = get_request_by_name( name ) # Make sure the request is showing in the 'new' filter @@ -363,7 +366,7 @@ class TestFormsAndRequests( TwillTestCas self.submit_request( cntrller='requests_admin', request_id=self.security.encode_id( request_two.id ), request_name=request_two.name, - strings_displayed_after_submit=[ 'The request <b>%s</b> has been submitted.' % request_two.name ] ) + strings_displayed_after_submit=[ 'The request has been submitted.' ] ) refresh( request_two ) # Make sure the request is showing in the 'submitted' filter self.check_request_grid( cntrller='requests_admin', @@ -383,7 +386,7 @@ class TestFormsAndRequests( TwillTestCas request_name=request_two.name, comment="Rejection test comment", strings_displayed=[ 'Reject Sequencing Request "%s"' % request_two.name ], - strings_displayed_after_submit=[ 'Request <b>%s</b> has been rejected.' % request_two.name ] ) + strings_displayed_after_submit=[ 'Request (%s) has been rejected.' % request_two.name ] ) refresh( request_two ) # Make sure the request is showing in the 'rejected' filter self.check_request_grid( cntrller='requests_admin', --- a/templates/requests/common/edit_request.mako +++ /dev/null @@ -1,97 +0,0 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> - -%if message: - ${render_msg( message, status )} -%endif - -<br/> -<br/> -<ul class="manage-table-actions"> - <li> - <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='show', id=trans.security.encode_id(request.id))}"> - <span>Browse this request</span></a> - </li> - <li> - <a class="action-button" href="${h.url_for( controller=cntrller, action='list')}"> - <span>Browse all requests</span></a> - </li> -</ul> - -<div class="toolForm"> - <div class="toolFormTitle">Edit sequencing request "${request.name}"</div> - <div class="toolFormBody"> - <form name="edit_request" id="edit_request" action="${h.url_for( controller='requests_common', cntrller=cntrller, action='edit', id=trans.security.encode_id(request.id))}" method="post" > - %for i, field in enumerate(widgets): - <div class="form-row"> - <label>${field['label']}</label> - ${field['widget'].get_html()} - %if field['label'] == 'Data library' and new_library: - ${new_library.get_html()} - %endif - <div class="toolParamHelp" style="clear: both;"> - ${field['helptext']} - </div> - <div style="clear: both"></div> - </div> - %endfor - <div class="form-row"> - <div style="float: left; width: 250px; margin-right: 10px;"> - <input type="hidden" name="refresh" value="true" size="40"/> - </div> - <div style="clear: both"></div> - </div> - <div class="form-row"> - <input type="submit" name="save_changes_request_button" value="Save"/> - </div> - </form> - </div> - <div class="toolFormTitle">Email notification settings</div> - <div class="toolFormBody"> - <form name="settings" id="settings" action="${h.url_for( controller='requests_common', cntrller=cntrller, action='email_settings', id=trans.security.encode_id(request.id))}" method="post" > - <% - email_user = '' - email_additional = [] - for e in request.notification['email']: - if e == request.user.email: - email_user = 'checked' - else: - email_additional.append(e) - emails = '\r\n'.join(email_additional) - - %> - - <div class="form-row"> - <label>Send to:</label> - <input type="checkbox" name="email_user" value="true" ${email_user}>${request.user.email} (Sequencing request owner)<input type="hidden" name="email_user" value="true"> - </div> - <div class="form-row"> - <label>Additional email addresses:</label> - <textarea name="email_additional" rows="3" cols="40">${emails}</textarea> - <div class="toolParamHelp" style="clear: both;"> - Enter one email address per line - </div> - </div> - <div class="form-row"> - <label>Select sample state(s) to send email notification:</label> - %for ss in request.type.states: - <% - email_state = '' - if ss.id in request.notification['sample_states']: - email_state = 'checked' - %> - <input type="checkbox" name=sample_state_${ss.id} value="true" ${email_state} >${ss.name}<input type="hidden" name=sample_state_${ss.id} value="true"> - <br/> - %endfor - <div class="toolParamHelp" style="clear: both;"> - Email notification would be sent when all the samples of this sequencing request are in the selected state(s). - </div> - </div> - - <div class="form-row"> - <input type="submit" name="save_button" value="Save"/> - </div> - </form> - </div> - -</div> --- a/templates/admin/forms/show_form_read_only.mako +++ b/templates/admin/forms/show_form_read_only.mako @@ -1,7 +1,6 @@ <%inherit file="/base.mako"/><%namespace file="/message.mako" import="render_msg" /> - %if message: ${render_msg( message, status )} %endif @@ -64,32 +63,32 @@ </%def><div class="toolForm"> - %if form.desc: - <div class="toolFormTitle">${form.name} - <i> ${form.desc}</i> (${form.type}) - <a id="form-${form.id}-popup" class="popup-arrow" style="display: none;">▼</a> - <div popupmenu="form-${form.id}-popup"> - <a class="action-button" href="${h.url_for( controller='forms', action='manage', operation='Edit', id=trans.security.encode_id(form.current.id) )}">Edit</a> + %if form_definition.desc: + <div class="toolFormTitle">${form_definition.name} - <i> ${form_definition.desc}</i> (${form_definition.type}) + <a id="form_definition-${form_definition.id}-popup" class="popup-arrow" style="display: none;">▼</a> + <div popupmenu="form_definition-${form_definition.id}-popup"> + <a class="action-button" href="${h.url_for( controller='forms', action='manage', operation='Edit', id=trans.security.encode_id(form_definition.current.id) )}">Edit</a></div></div> %else: - <div class="toolFormTitle">${form.name} (${form.type}) - <a id="form-${form.id}-popup" class="popup-arrow" style="display: none;">▼</a> - <div popupmenu="form-${form.id}-popup"> - <a class="action-button" href="${h.url_for( controller='forms', action='manage', operation='Edit', id=trans.security.encode_id(form.current.id) )}">Edit</a> + <div class="toolFormTitle">${form_definition.name} (${form_definition.type}) + <a id="form_definition-${form_definition.id}-popup" class="popup-arrow" style="display: none;">▼</a> + <div popupmenu="form_definition-${form_definition.id}-popup"> + <a class="action-button" href="${h.url_for( controller='forms', action='manage', operation='Edit', id=trans.security.encode_id(form_definition.current.id) )}">Edit</a></div></div> %endif <form name="library" action="${h.url_for( controller='forms', action='manage' )}" method="post" > - %if form.type == trans.app.model.FormDefinition.types.SAMPLE: - %if not len(form.layout): - ${render_grid( 0, '', form.fields_of_grid( None ) )} + %if form_definition.type == trans.app.model.FormDefinition.types.SAMPLE: + %if not len(form_definition.layout): + ${render_grid( 0, '', form_definition.fields_of_grid( None ) )} %else: - %for grid_index, grid_name in enumerate(form.layout): - ${render_grid( grid_index, grid_name, form.fields_of_grid( grid_index ) )} + %for grid_index, grid_name in enumerate(form_definition.layout): + ${render_grid( grid_index, grid_name, form_definition.fields_of_grid( grid_index ) )} %endfor %endif %else: - %for index, field in enumerate(form.fields): + %for index, field in enumerate(form_definition.fields): <div class="form-row"><label>${field['label']}</label> %if field['helptext']: --- a/templates/admin/requests/reject.mako +++ b/templates/admin/requests/reject.mako @@ -8,18 +8,16 @@ <h2>Reject Sequencing Request "${request.name}"</h2><ul class="manage-table-actions"><li> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='events', id=trans.security.encode_id(request.id) )}"> - <span>Events</span></a> + <a class="action-button" href="${h.url_for( controller='requests_common', action='request_events', cntrller=cntrller, id=trans.security.encode_id(request.id) )}">Events</a></li><li> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='show', id=trans.security.encode_id(request.id) )}"> - <span>Browse this request</span></a> + <a class="action-button" href="${h.url_for( controller='requests_common', action='manage_request', cntrller=cntrller, id=trans.security.encode_id(request.id) )}">Browse this request</a></li></ul><div class="toolForm"><div class="toolFormTitle">Reject request</div> - <form name="event" action="${h.url_for( controller='requests_admin', action='reject', id=trans.security.encode_id(request.id))}" method="post" > + <form name="event" action="${h.url_for( controller='requests_admin', action='reject', id=trans.security.encode_id( request.id ) )}" method="post" ><div class="form-row"> Rejecting this request will move the request state to <b>Rejected</b>. </div> --- a/templates/requests/common/new_request.mako +++ /dev/null @@ -1,70 +0,0 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> - -%if message: - ${render_msg( message, status )} -%endif - -<%def name="javascripts()"> - ${parent.javascripts()} - ${h.js("jquery.autocomplete", "autocomplete_tagging" )} -</%def> - -<%def name="stylesheets()"> - ${parent.stylesheets()} - ${h.css( "autocomplete_tagging" )} -</%def> - -<br/> -<br/> -<ul class="manage-table-actions"> - <li> - <a class="action-button" href="${h.url_for( controller=cntrller, cntrller=cntrller, action='list')}"> - <span>Browse requests</span></a> - </li> -</ul> - -<div class="toolForm"> - <div class="toolFormTitle">Add a new request</div> - %if len(select_request_type.options) == 1: - There are no sequencer configurations available to ${trans.user.email} to create sequencing requests. - %else: - <div class="toolFormBody"> - <form name="new_request" id="new_request" action="${h.url_for( controller='requests_common', action='new', cntrller=cntrller)}" method="post" > - <div class="form-row"> - <label>Select a sequencer configuration:</label> - ${select_request_type.get_html()} - %if cntrller != 'requests_admin': - <div class="toolParamHelp" style="clear: both;"> - Please contact the lab manager if you are <br/> - not sure about the sequencer configuration. - </div> - %endif - </div> - - %if select_request_type.get_selected( return_label=True, return_value=True ) != ('Select one', 'none'): - %for i, field in enumerate(widgets): - <div class="form-row"> - <label>${field['label']}</label> - ${field['widget'].get_html()} - <div class="toolParamHelp" style="clear: both;"> - ${field['helptext']} - </div> - <div style="clear: both"></div> - </div> - %endfor - <div class="form-row"> - <input type="submit" name="create_request_button" value="Save"/> - <input type="submit" name="create_request_samples_button" value="Add samples"/> - </div> - %endif - <div class="form-row"> - <div style="float: left; width: 250px; margin-right: 10px;"> - <input type="hidden" name="refresh" value="true" size="40"/> - </div> - <div style="clear: both"></div> - </div> - </form> - </div> -</div> -%endif --- a/templates/requests/find_index.mako +++ /dev/null @@ -1,16 +0,0 @@ -<%inherit file="/webapps/galaxy/base_panels.mako"/> - -<%def name="init()"> -<% - self.has_left_panel=False - self.has_right_panel=False - self.active_view="requests" - self.message_box_visible=False -%> -</%def> - -<%def name="center_panel()"> - - <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${h.url_for( controller="requests_common", action="find" )}"></iframe> - -</%def> --- a/lib/galaxy/web/form_builder.py +++ b/lib/galaxy/web/form_builder.py @@ -515,3 +515,55 @@ def get_suite(): """Get unittest suite for this module""" import doctest, sys return doctest.DocTestSuite( sys.modules[__name__] ) + +# --------- Utility methods ----------------------------- + +def build_select_field( trans, objs, label_attr, select_field_name, initial_value='none', + selected_value='none', refresh_on_change=False, multiple=False, display=None, size=None ): + """ + Build a SelectField given a set of objects. The received params are: + - objs: the set of object used to populate the option list + - label_attr: the attribute of each obj (e.g., name, email, etc ) whose value is used to populate each option label. If the string + 'self' is passed as label_attr, each obj in objs is assumed to be a string, so the obj itself is used + - select_field_name: the name of the SelectField + - initial_value: the vlaue of the first option in the SelectField - allows for an option telling the user to select something + - selected_value: the value of the currently selected option + - refresh_on_change: True if the SelectField should perform a refresh_on_change + """ + values = [ initial_value ] + for obj in objs: + if label_attr == 'self': + # Each obj is a string + values.append( obj ) + else: + values.append( trans.security.encode_id( obj.id ) ) + if refresh_on_change: + refresh_on_change_values = values + else: + refresh_on_change_values = [] + select_field = SelectField( name=select_field_name, + multiple=multiple, + display=display, + refresh_on_change=refresh_on_change, + refresh_on_change_values=refresh_on_change_values, + size=size ) + if display is None: + # only insert an initial "Select one" option if we are not displaying check boxes or radio buttons + if selected_value == initial_value: + select_field.add_option( 'Select one', initial_value, selected=True ) + else: + select_field.add_option( 'Select one', initial_value ) + for obj in objs: + if label_attr == 'self': + # Each obj is a string + if str( selected_value ) == str( obj ): + select_field.add_option( obj, obj, selected=True ) + else: + select_field.add_option( obj, obj ) + else: + label = getattr( obj, label_attr ) + if str( selected_value ) == str( obj.id ) or str( selected_value ) == trans.security.encode_id( obj.id ): + select_field.add_option( label, trans.security.encode_id( obj.id ), selected=True ) + else: + select_field.add_option( label, trans.security.encode_id( obj.id ) ) + return select_field --- a/templates/admin/requests/get_data.mako +++ b/templates/admin/requests/get_data.mako @@ -1,20 +1,16 @@ <%inherit file="/base.mako"/><%namespace file="/message.mako" import="render_msg" /> - <script type="text/javascript"> $(document).ready(function(){ //hide the all of the element with class msg_body $(".msg_body").hide(); - //toggle the componenet with class msg_body + //toggle the component with class msg_body $(".msg_head").click(function(){ $(this).next(".msg_body").slideToggle(450); }); }); - - - </script><script type="text/javascript"> @@ -30,7 +26,7 @@ type: "POST", url: "${h.url_for( controller='requests_admin', action='get_file_details' )}", dataType: "json", - data: { id: request_id, folder_path: document.get_data.folder_path.value+selected_value }, + data: { id: request_id, folder_path: document.get_data.folder_path.value + selected_value }, success : function ( data ) { cell.html( '<label>'+data+'</label>' ) } @@ -40,13 +36,11 @@ { cell.html( '' ) } - - } </script><script type="text/javascript"> - function open_folder1(request_id, folder_path) + function open_folder1( request_id, folder_path ) { var w = document.get_data.files_list.selectedIndex; var selected_value = document.get_data.files_list.options[w].value; @@ -79,14 +73,11 @@ } </script> - <style type="text/css"> .msg_head { padding: 0px 0px; cursor: pointer; } - -} </style> %if message: @@ -96,23 +87,23 @@ <br/><ul class="manage-table-actions"><li> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_request_types', operation='view', id=trans.security.encode_id(request.type.id) )}"> + <a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_request_types', operation='view', id=trans.security.encode_id( request.type.id ) )}"><span>Sequencer configuration "${request.type.name}"</span></a></li><li> - <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='show', id=trans.security.encode_id(request.id) )}"> + <a class="action-button" href="${h.url_for( controller='requests_common', action='manage_request', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}"><span>Browse this request</span></a></li></ul> -<form name="get_data" id="get_data" action="${h.url_for( controller='requests_admin', cntrller=cntrller, action='get_data', request_id=request.id)}" method="post" > +<form name="get_data" id="get_data" action="${h.url_for( controller='requests_admin', cntrller=cntrller, action='get_data', request_id=trans.security.encode_id( request.id )}" method="post" ><div class="toolForm"><div class="toolFormTitle">Select files for transfer</div><div class="form-row"><label>Sample:</label> - ${samples_selectbox.get_html()} + ${sample_id_select_field.get_html()} <div class="toolParamHelp" style="clear: both;"> - Select the sample with which you want to associate the dataset(s) + Select the sample with which you want to associate the datasets </div><br/><label>Folder path on the sequencer:</label> @@ -122,14 +113,11 @@ </div><div class="form-row"><select name="files_list" id="files_list" style="max-width: 60%; width: 98%; height: 150px; font-size: 100%;" ondblclick="open_folder1(${request.id}, '${folder_path}')" onChange="display_file_details(${request.id}, '${folder_path}')" multiple> - %for index, f in enumerate(files): + %for index, f in enumerate( files ): <option value="${f}">${f}</option> %endfor </select><br/> - <div id="file_details" class="toolParamHelp" style="clear: both;"> - - </div></div><div class="form-row"><input type="submit" name="select_show_datasets_button" value="Select & show datasets"/> --- a/templates/admin/requests/rename_datasets.mako +++ b/templates/admin/requests/rename_datasets.mako @@ -10,16 +10,14 @@ <ul class="manage-table-actions"><li> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_datasets', sample_id=sample.id )}"> - <span>Browse datasets</span></a> + <a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_datasets', sample_id=trans.security.encode_id( sample.id ) )}">Browse datasets</a></li><li> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='show', id=trans.security.encode_id(sample.request.id) )}"> - <span>Browse this request</span></a> + <a class="action-button" href="${h.url_for( controller='requests_common', action='manage_request', cntrller=cntrller, id=trans.security.encode_id( sample.request.id ) )}">Browse this request</a></li></ul> -<form name="rename_datasets" id="rename_datasets" action="${h.url_for( controller='requests_admin', action='rename_datasets', id_list=id_list, sample_id=trans.security.encode_id(sample.id))}" method="post" > +<form name="rename_datasets" id="rename_datasets" action="${h.url_for( controller='requests_admin', action='rename_datasets', id_list=id_list, sample_id=trans.security.encode_id( sample.id ) )}" method="post" ><table class="grid"><thead><tr> --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -5,21 +5,19 @@ Naming: try to use class names that have the relationship cardinalities are obvious (e.g. prefer Dataset to Data) """ -import os.path, os, errno, sys, codecs, operator import galaxy.datatypes from galaxy.util.bunch import Bunch from galaxy import util -import tempfile import galaxy.datatypes.registry from galaxy.datatypes.metadata import MetadataCollection from galaxy.security import RBACAgent, get_permitted_actions from galaxy.util.hash_util import * from galaxy.web.form_builder import * from galaxy.model.item_attrs import UsesAnnotations -import logging, smtplib, socket +from sqlalchemy.orm import object_session +import os.path, os, errno, codecs, operator, smtplib, socket, pexpect, logging + log = logging.getLogger( __name__ ) -from sqlalchemy.orm import object_session -import pexpect datatypes_registry = galaxy.datatypes.registry.Registry() #Default Value Required for unit tests @@ -41,7 +39,6 @@ class User( object ): # Relationships self.histories = [] self.credentials = [] - def set_password_cleartext( self, cleartext ): """Set 'self.password' to the digest of 'cleartext'.""" self.password = new_secure_hash( text_type=cleartext ) @@ -55,8 +52,8 @@ class User( object ): if role not in roles: roles.append( role ) return roles - def accessible_libraries(self, trans, actions): - # get all permitted libraries for this user + def accessible_libraries( self, trans, actions ): + # Get all permitted libraries for this user all_libraries = trans.sa_session.query( trans.app.model.Library ) \ .filter( trans.app.model.Library.table.c.deleted == False ) \ .order_by( trans.app.model.Library.name ) @@ -74,18 +71,19 @@ class User( object ): if can_show: libraries[ library ] = hidden_folder_ids return libraries - def accessible_request_types(self, trans): - # get all permitted libraries for this user - all_rt_list = trans.sa_session.query( trans.app.model.RequestType ) \ - .filter( trans.app.model.RequestType.table.c.deleted == False ) \ - .order_by( trans.app.model.RequestType.name ) - roles = self.all_roles() - rt_list = [] - for rt in all_rt_list: - for permission in rt.actions: - if permission.role.id in [r.id for r in roles]: - rt_list.append(rt) - return list(set(rt_list)) + def accessible_request_types( self, trans ): + active_request_types = trans.sa_session.query( trans.app.model.RequestType ) \ + .filter( trans.app.model.RequestType.table.c.deleted == False ) \ + .order_by( trans.app.model.RequestType.name ) + # Filter active_request_types to those that can be accessed by this user + role_ids = [ r.id for r in self.all_roles() ] + accessible_request_types = set() + for request_type in active_request_types: + for permission in request_type.actions: + if permission.role.id in role_ids: + accessible_request_types.add( request_type ) + accessible_request_types = [ request_type for request_type in accessible_request_types ] + return accessible_request_types class Job( object ): """ @@ -1505,12 +1503,12 @@ class FormDefinition( object ): params = util.Params( kwd ) widgets = [] for index, field in enumerate( self.fields ): + field_type = field[ 'type' ] field_name = 'field_%i' % index # determine the value of the field if field_name in kwd: - # the user had already filled out this field and the same form is re-rendered - # due to some reason like required fields have been left out. - if field[ 'type' ] == 'CheckboxField': + # The form was submitted via refresh_on_change + if field_type == 'CheckboxField': value = CheckboxField.is_checked( params.get( field_name, False ) ) else: value = util.restore_text( params.get( field_name, '' ) ) @@ -1521,7 +1519,7 @@ class FormDefinition( object ): except: # If there was an error getting the saved value, we'll still # display the widget, but it will be empty. - if field[ 'type' ] == 'CheckboxField': + if field_type == 'CheckboxField': # Since we do not have contents, set checkbox value to False value = False else: @@ -1529,21 +1527,21 @@ class FormDefinition( object ): value = '' else: # if none of the above, then leave the field empty - if field[ 'type' ] == 'CheckboxField': + if field_type == 'CheckboxField': # Since we do not have contents, set checkbox value to False value = False else: # Set other field types to the default value of the field value = field.get('default', '') - # create the field widget - field_widget = eval( field[ 'type' ] )( field_name ) - if field[ 'type' ] == 'TextField': + # Create the field widget + field_widget = eval( field_type )( field_name ) + if field_type == 'TextField': field_widget.set_size( 40 ) field_widget.value = value - elif field[ 'type' ] == 'TextArea': + elif field_type == 'TextArea': field_widget.set_size( 3, 40 ) field_widget.value = value - elif field['type'] == 'AddressField': + elif field_type == 'AddressField': field_widget.user = user field_widget.value = value field_widget.params = params @@ -1551,13 +1549,13 @@ class FormDefinition( object ): field_widget.user = user field_widget.value = value field_widget.params = params - elif field[ 'type' ] == 'SelectField': + elif field_type == 'SelectField': for option in field[ 'selectlist' ]: if option == value: field_widget.add_option( option, option, selected=True ) else: field_widget.add_option( option, option ) - elif field[ 'type' ] == 'CheckboxField': + elif field_type == 'CheckboxField': field_widget.set_checked( value ) if field[ 'required' ] == 'required': req = 'Required' @@ -1586,8 +1584,7 @@ class Request( object ): SUBMITTED = 'In Progress', REJECTED = 'Rejected', COMPLETE = 'Complete' ) - def __init__(self, name=None, desc=None, request_type=None, user=None, - form_values=None, notification=None): + def __init__( self, name=None, desc=None, request_type=None, user=None, form_values=None, notification=None ): self.name = name self.desc = desc self.type = request_type @@ -1595,59 +1592,79 @@ class Request( object ): self.user = user self.notification = notification self.samples_list = [] - def state(self): + @property + def state( self ): + latest_event = self.latest_event + if latest_event: + return latest_event.state + return None + @property + def latest_event( self ): if self.events: - return self.events[0].state + return self.events[0] return None - def common_state(self): - ''' - This method returns the state of this request's sample when they are all - in one common state. If not this returns None - ''' + @property + def samples_have_common_state( self ): + """ + Returns the state of this request's samples when they are all + in one common state. Otherwise returns False. + """ + state_for_comparison = self.samples[0].state + if state_for_comparison is None: + for s in self.samples: + if s.state is not None: + return False for s in self.samples: - if s.current_state().id != self.samples[0].current_state().id: + if s.state.id != state_for_comparison.id: return False - return self.samples[0].current_state() - def last_comment(self): - if self.events: - if self.events[0].comment: - return self.events[0].comment - else: - return '' + return state_for_comparison + @property + def last_comment( self ): + latest_event = self.latest_event + if latest_event: + if latest_event.comment: + return latest_event.comment + return '' return 'No comment' - def has_sample(self, sample_name): + def has_sample( self, sample_name ): for s in self.samples: if s.name == sample_name: return s return False - def unsubmitted(self): - return self.state() in [ self.states.REJECTED, self.states.NEW ] - def rejected(self): - return self.state() == self.states.REJECTED - def submitted(self): - return self.state() == self.states.SUBMITTED - def new(self): - return self.state() == self.states.NEW - def complete(self): - return self.state() == self.states.COMPLETE - def sequence_run_ready(self): + @property + def is_unsubmitted( self ): + return self.state in [ self.states.REJECTED, self.states.NEW ] + @property + def is_rejected( self ): + return self.state == self.states.REJECTED + @property + def is_submitted( self ): + return self.state == self.states.SUBMITTED + @property + def is_new( self ): + return self.state == self.states.NEW + @property + def is_complete( self ): + return self.state == self.states.COMPLETE + @property + def has_samples_without_library_destinations( self ): + # Return all samples that are not associated with a library samples = [] for s in self.samples: if not s.library: - samples.append(s.name) + samples.append( s.name ) return samples - def send_email_notification(self, trans, common_state, final_state=False): - # check if an email notification is configured to be sent when the samples + def send_email_notification( self, trans, common_state, final_state=False ): + # Check if an email notification is configured to be sent when the samples # are in this state - if common_state.id not in self.notification['sample_states']: + if self.notification and common_state.id not in self.notification[ 'sample_states' ]: return comments = '' - # send email - if self.notification['email'] and trans.app.config.smtp_server is not None: - host = trans.request.host.split(':')[0] - if host in ['localhost', '127.0.0.1']: + # Send email + if trans.app.config.smtp_server is not None and self.notification and self.notification[ 'email' ]: + host = trans.request.host.split( ':' )[0] + if host in [ 'localhost', '127.0.0.1' ]: host = socket.getfqdn() - body = """ Galaxy Sample Tracking Notification =================================== @@ -1662,39 +1679,39 @@ Number of samples: %(num_samples) All samples in state: %(sample_state)s """ - values = dict(user=self.user.email, - request_name=self.name, - request_type=self.type.name, - request_state=self.state(), - num_samples=str(len(self.samples)), - sample_state=common_state.name, - create_time=self.create_time, - submit_time=self.create_time) + values = dict( user=self.user.email, + request_name=self.name, + request_type=self.type.name, + request_state=self.state, + num_samples=str( len( self.samples ) ), + sample_state=common_state.name, + create_time=self.create_time, + submit_time=self.create_time ) body = body % values # check if this is the final state of the samples if final_state: txt = "Sample Name -> Data Library/Folder\r\n" for s in self.samples: - txt = txt + "%s -> %s/%s\r\n" % (s.name, s.library.name, s.folder.name) + txt = txt + "%s -> %s/%s\r\n" % ( s.name, s.library.name, s.folder.name ) body = body + txt to = self.notification['email'] frm = 'galaxy-no-reply@' + host subject = "Galaxy Sample Tracking notification: '%s' sequencing request" % self.name - message = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s" % (frm, ", ".join(to), subject, body) + message = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s" % ( frm, ", ".join( to ), subject, body ) try: s = smtplib.SMTP() - s.connect(trans.app.config.smtp_server) - s.sendmail(frm, to, message) + s.connect( trans.app.config.smtp_server ) + s.sendmail( frm, to, message ) s.quit() - comments = "Email notification sent to %s." % ", ".join(to).strip().strip(',') + comments = "Email notification sent to %s." % ", ".join( to ).strip().strip( ',' ) except: comments = "Email notification failed." # update the request history with the email notification event elif not trans.app.config.smtp_server: comments = "Email notification failed as SMTP server not set in config file" if comments: - event = trans.app.model.RequestEvent(self, self.state(), comments) - trans.sa_session.add(event) + event = trans.app.model.RequestEvent( self, self.state, comments ) + trans.sa_session.add( event ) trans.sa_session.flush() return comments @@ -1710,37 +1727,16 @@ class RequestType( object ): EXPERIMENT_NAME = 'Prepend experiment name', EXPERIMENT_AND_SAMPLE_NAME = 'Prepend experiment and sample name') permitted_actions = get_permitted_actions( filter='REQUEST_TYPE' ) - def __init__(self, name=None, desc=None, request_form=None, sample_form=None, - datatx_info=None): + def __init__( self, name=None, desc=None, request_form=None, sample_form=None, datatx_info=None ): self.name = name self.desc = desc self.request_form = request_form self.sample_form = sample_form self.datatx_info = datatx_info - def last_state(self): + @property + def state( self ): + # The states mapper for this object orders ascending return self.states[-1] - - def change_state_widgets(self, trans, sample=None): - if sample: - curr_state = sample.current_state() - else: - curr_state = self.states[0] - states_input = SelectField('select_state') - for state in self.states: - if curr_state.name == state.name: - states_input.add_option(state.name, state.id, selected=True) - else: - states_input.add_option(state.name, state.id) - widgets = [] - if sample: - widgets.append(('Select the new state of the sample from the list of possible state(s)', - states_input)) - else: - widgets.append(('Select the new state of the selected sample(s) from the list of possible state(s)', - states_input)) - widgets.append(('Comments', TextField('comment', 50))) - title = 'Change current state' - return widgets, title class RequestTypePermissions( object ): def __init__( self, action, request_type, role ): @@ -1749,16 +1745,15 @@ class RequestTypePermissions( object ): self.role = role class Sample( object ): - bulk_operations = Bunch(CHANGE_STATE = 'Change state', - SELECT_LIBRARY = 'Select data library and folder') + bulk_operations = Bunch( CHANGE_STATE = 'Change state', + SELECT_LIBRARY = 'Select data library and folder' ) transfer_status = Bunch( NOT_STARTED = 'Not started', IN_QUEUE = 'In queue', TRANSFERRING = 'Transferring dataset', ADD_TO_LIBRARY = 'Adding to data library', COMPLETE = 'Complete', - ERROR = 'Error') - def __init__(self, name=None, desc=None, request=None, form_values=None, - bar_code=None, library=None, folder=None): + ERROR = 'Error' ) + def __init__(self, name=None, desc=None, request=None, form_values=None, bar_code=None, library=None, folder=None): self.name = name self.desc = desc self.request = request @@ -1766,29 +1761,39 @@ class Sample( object ): self.bar_code = bar_code self.library = library self.folder = folder - def current_state(self): + @property + def state( self ): + latest_event = self.latest_event + if latest_event: + return latest_event.state + return None + @property + def latest_event( self ): if self.events: - return self.events[0].state + return self.events[0] return None - def untransferred_dataset_files(self): + @property + def untransferred_dataset_files( self ): count = 0 for df in self.datasets: if df.status == self.transfer_status.NOT_STARTED: count = count + 1 return count - def inprogress_dataset_files(self): + @property + def inprogress_dataset_files( self ): count = 0 for df in self.datasets: if df.status not in [self.transfer_status.NOT_STARTED, self.transfer_status.COMPLETE]: count = count + 1 return count - def transferred_dataset_files(self): + @property + def transferred_dataset_files( self ): count = 0 for df in self.datasets: if df.status == self.transfer_status.COMPLETE: count = count + 1 return count - def dataset_size(self, filepath): + def dataset_size( self, filepath ): def print_ticks(d): pass datatx_info = self.request.type.datatx_info --- a/lib/galaxy/web/controllers/requests.py +++ b/lib/galaxy/web/controllers/requests.py @@ -1,203 +1,74 @@ from galaxy.web.base.controller import * -from galaxy.web.framework.helpers import time_ago, iff, grids +from galaxy.web.framework.helpers import grids from galaxy.model.orm import * -from galaxy.datatypes import sniff from galaxy import model, util -from galaxy.util.streamball import StreamBall -from galaxy.util.odict import odict -import logging, tempfile, zipfile, tarfile, os, sys -from galaxy.web.form_builder import * -from datetime import datetime, timedelta -from cgi import escape, FieldStorage +from galaxy.web.form_builder import * +from galaxy.web.controllers.requests_common import RequestsGrid +import logging log = logging.getLogger( __name__ ) -class RequestsGrid( grids.Grid ): - # Custom column types - class NameColumn( grids.TextColumn ): - def get_value(self, trans, grid, request): - return request.name - class DescriptionColumn( grids.TextColumn ): - def get_value(self, trans, grid, request): - return request.desc - class SamplesColumn( grids.GridColumn ): - def get_value(self, trans, grid, request): - return str(len(request.samples)) - class TypeColumn( grids.TextColumn ): - def get_value(self, trans, grid, request): - return request.type.name - class StateColumn( grids.GridColumn ): - def __init__( self, col_name, key, model_class, event_class, filterable, link ): - grids.GridColumn.__init__(self, col_name, key=key, model_class=model_class, filterable=filterable, link=link) - self.event_class = event_class - def get_value(self, trans, grid, request): - if request.state() == request.states.REJECTED: - return '<div class="count-box state-color-error">%s</div>' % request.state() - elif request.state() == request.states.NEW: - return '<div class="count-box state-color-queued">%s</div>' % request.state() - elif request.state() == request.states.SUBMITTED: - return '<div class="count-box state-color-running">%s</div>' % request.state() - elif request.state() == request.states.COMPLETE: - return '<div class="count-box state-color-ok">%s</div>' % request.state() - return request.state() - def filter( self, db_session, user, query, column_filter ): - """ Modify query to filter request by state. """ - if column_filter == "All": - return query - if column_filter: - # select r.id, r.name, re.id, re.state - # from request as r, request_event as re - # where re.request_id=r.id and re.state='Complete' and re.create_time in - # (select MAX( create_time) - # from request_event - # group by request_id) - q = query.join(self.event_class.table)\ - .filter( self.model_class.table.c.id==self.event_class.table.c.request_id )\ - .filter( self.event_class.table.c.state==column_filter )\ - .filter( self.event_class.table.c.id.in_(select(columns=[func.max(self.event_class.table.c.id)], - from_obj=self.event_class.table, - group_by=self.event_class.table.c.request_id))) - return q - def get_accepted_filters( self ): - """ Returns a list of accepted filters for this column. """ - accepted_filter_labels_and_vals = [ model.Request.states.NEW, - model.Request.states.REJECTED, - model.Request.states.SUBMITTED, - model.Request.states.COMPLETE, - "All"] - accepted_filters = [] - for val in accepted_filter_labels_and_vals: - label = val.lower() - args = { self.key: val } - accepted_filters.append( grids.GridColumnFilter( label, args) ) - return accepted_filters - # Grid definition - title = "Sequencing Requests" - template = 'requests/grid.mako' - model_class = model.Request - default_sort_key = "create_time" - num_rows_per_page = 50 - preserve_state = True - use_paging = True - default_filter = dict( deleted="False") - columns = [ - NameColumn( "Name", - key="name", - model_class=model.Request, - link=( lambda item: iff( item.deleted, None, dict( operation="show", id=item.id ) ) ), - attach_popup=True, - filterable="advanced" ), - DescriptionColumn( "Description", - key='desc', - model_class=model.Request, - filterable="advanced" ), - SamplesColumn( "Sample(s)", - link=( lambda item: iff( item.deleted, None, dict( operation="show", id=item.id ) ) ), ), - TypeColumn( "Sequencer" ), - grids.GridColumn( "Last Updated", key="update_time", format=time_ago ), - grids.DeletedColumn( "Deleted", - key="deleted", - visible=False, - filterable="advanced" ), - StateColumn( "State", - model_class=model.Request, - event_class=model.RequestEvent, - key='state', - filterable="advanced", - link=( lambda item: iff( item.deleted, None, dict( operation="events", id=item.id ) ) ) ) - ] - columns.append( grids.MulticolFilterColumn( "Search", - cols_to_filter=[ columns[0], columns[1] ], - key="free-text-search", - visible=False, - filterable="standard" ) ) - operations = [ - grids.GridOperation( "Submit", allow_multiple=False, condition=( lambda item: not item.deleted and item.unsubmitted() and item.samples ), - confirm="More samples cannot be added to this request once it is submitted. Click OK to submit." ), - grids.GridOperation( "Edit", allow_multiple=False, condition=( lambda item: not item.deleted and item.unsubmitted() ) ), - grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: not item.deleted and item.new() ) ), - grids.GridOperation( "Undelete", allow_multiple=True, condition=( lambda item: item.deleted ) ) - - ] +class UserRequestsGrid( RequestsGrid ): + operations = [ operation for operation in RequestsGrid.operations ] + operations.append( grids.GridOperation( "Edit", allow_multiple=False, condition=( lambda item: not item.deleted and item.is_unsubmitted ) ) ) + operations.append( grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: not item.deleted and item.is_new ) ) ) + operations.append( grids.GridOperation( "Undelete", allow_multiple=True, condition=( lambda item: item.deleted ) ) ) global_actions = [ grids.GridAction( "Create new request", dict( controller='requests_common', - cntrller='requests', - action='new', - select_request_type='True' ) ) + action='create_request', + cntrller='requests' ) ) ] def apply_query_filter( self, trans, query, **kwd ): + # gvk ( 9/28/10 ) TODO: is this method needed? return query.filter_by( user=trans.user ) class Requests( BaseController ): - request_grid = RequestsGrid() + request_grid = UserRequestsGrid() @web.expose - @web.require_login( "create/submit sequencing requests" ) + @web.require_login( "view sequencing requests" ) def index( self, trans ): return trans.fill_template( "requests/index.mako" ) - @web.expose @web.require_login( "create/submit sequencing requests" ) - def find_index( self, trans ): - return trans.fill_template( "requests/find_index.mako" ) - + def find_samples_index( self, trans ): + return trans.fill_template( "requests/find_samples_index.mako" ) @web.expose - @web.require_login( "create/submit sequencing requests" ) - def list( self, trans, **kwd ): - ''' - List all request made by the current user - ''' - + def browse_requests( self, trans, **kwd ): if 'operation' in kwd: operation = kwd['operation'].lower() - if not kwd.get( 'id', None ): - return trans.response.send_redirect( web.url_for( controller='requests', - action='list', - status='error', - message="Invalid request ID") ) - if operation == "show": + if operation == "edit": return trans.response.send_redirect( web.url_for( controller='requests_common', + action='edit_basic_request_info', cntrller='requests', - action='show', **kwd ) ) - elif operation == "submit": + if operation == "manage_request": return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', cntrller='requests', - action='submit', **kwd ) ) - elif operation == "delete": + if operation == "delete": return trans.response.send_redirect( web.url_for( controller='requests_common', + action='delete_request', cntrller='requests', - action='delete', **kwd ) ) - elif operation == "undelete": + if operation == "undelete": return trans.response.send_redirect( web.url_for( controller='requests_common', + action='undelete_request', cntrller='requests', - action='undelete', **kwd ) ) - elif operation == "edit": - return trans.response.send_redirect( web.url_for( controller='requests_common', - cntrller='requests', - action='edit', - show=True, **kwd ) ) - elif operation == "events": - return trans.response.send_redirect( web.url_for( controller='requests_common', - cntrller='requests', - action='events', - **kwd ) ) - # if there are one or more requests that has been rejected by the admin - # recently, then show a message as a reminder to the user - rlist = trans.sa_session.query( trans.app.model.Request ) \ - .filter( trans.app.model.Request.table.c.deleted==False ) \ - .filter( trans.app.model.Request.table.c.user_id==trans.user.id ) + # If there are requests that have been rejected, show a message as a reminder to the user rejected = 0 - for r in rlist: - if r.rejected(): + for request in trans.sa_session.query( trans.app.model.Request ) \ + .filter( trans.app.model.Request.table.c.deleted==False ) \ + .filter( trans.app.model.Request.table.c.user_id==trans.user.id ): + if request.is_rejected: rejected = rejected + 1 if rejected: - kwd['status'] = 'warning' - kwd['message'] = "%d requests (highlighted in red) were rejected, click on the request name for details." \ - % rejected + status = 'warning' + message = "%d requests (highlighted in red) were rejected. Click on the request name for details." % rejected + kwd[ 'status' ] = status + kwd[ 'message' ] = message # Render the list view return self.request_grid( trans, **kwd ) --- /dev/null +++ b/templates/requests/common/find_samples.mako @@ -0,0 +1,84 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + +<%def name="javascripts()"> + ${parent.javascripts()} + ${h.js("jquery.autocomplete", "autocomplete_tagging" )} +</%def> + +<%def name="stylesheets()"> + ${parent.stylesheets()} + ${h.css( "autocomplete_tagging" )} +</%def> + +<% is_admin = cntrller == 'requests_admin' and trans.user_is_admin() %> + +<br/> +<br/> +<ul class="manage-table-actions"> + <li> + <a class="action-button" href="${h.url_for( controller=cntrller, action='browse_requests' )}">Browse requests</a> + </li> +</ul> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">Find samples</div> + <div class="toolFormBody"> + <form name="find_request" id="find_request" action="${h.url_for( controller='requests_common', action='find_samples', cntrller=cntrller )}" method="post" > + <div class="form-row"> + <label>Find samples using:</label> + ${search_type.get_html()} + <div class="toolParamHelp" style="clear: both;"> + Select a sample attribute for searching. To search <br/> + for a sample with a dataset name, select the dataset <br/> + option above. This will return all the samples that <br/> + are associated with a dataset with that name. <br/> + </div> + </div> + <div class="form-row"> + <label>Show only sequencing requests in state:</label> + ${request_states.get_html()} + </div> + <div class="form-row"> + ${search_box.get_html()} + <input type="submit" name="find_samples_button" value="Find"/> + <div class="toolParamHelp" style="clear: both;"> + Wildcard search (%) can be used as placeholder for any sequence of characters or words.<br/> + For example, to search for samples starting with 'mysample' use 'mysample%' as the search string. + </div> + </div> + %if results: + <div class="form-row"> + <label><i>${results}</i></label> + %if samples: + <div class="toolParamHelp" style="clear: both;"> + The search results are sorted by the date the samples where created. + </div> + %endif + </div> + %endif + <div class="form-row"> + %if samples: + %for sample in samples: + <div class="form-row"> + Sample: <b>${sample.name}</b> | Barcode: ${sample.bar_code}<br/> + State: ${sample.state}<br/> + Datasets: <a href="${h.url_for( controller='requests_common', action='view_dataset_transfer', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${sample.transferred_dataset_files}/${len( sample.datasets )}</a><br/> + %if is_admin: + <i>User: ${sample.request.user.email}</i> + %endif + <div class="toolParamHelp" style="clear: both;"> + <a href="${h.url_for( controller='requests_common', action='manage_request', cntrller=cntrller, id=trans.security.encode_id( sample.request.id ) )}">Sequencing request: ${sample.request.name} | Type: ${sample.request.type.name} | State: ${sample.request.state}</a> + </div> + </div> + <br/> + %endfor + %endif + </div> + </form> + </div> +</div> --- a/templates/webapps/galaxy/base_panels.mako +++ b/templates/webapps/galaxy/base_panels.mako @@ -78,16 +78,14 @@ %> ## Lab menu. - %if trans.user and trans.user.requests: - <% - menu_options = [ - [ 'Sequencing Requests', h.url_for( controller='/requests', action='index' ) ], - [ 'Find Samples', h.url_for( controller='/requests', action='find_index' ) ], - [ 'Help', app.config.get( "lims_doc_url", "http://main.g2.bx.psu.edu/u/rkchak/p/sts" ), "galaxy_main" ] - ] - tab( "lab", "Lab", None, menu_options=menu_options ) - %> - %endif + <% + menu_options = [ + [ 'Sequencing Requests', h.url_for( controller='/requests', action='index' ) ], + [ 'Find Samples', h.url_for( controller='/requests', action='find_samples_index' ) ], + [ 'Help', app.config.get( "lims_doc_url", "http://main.g2.bx.psu.edu/u/rkchak/p/sts" ), "galaxy_main" ] + ] + tab( "lab", "Lab", None, menu_options=menu_options, visible=( trans.user and trans.user.requests ) ) + %> ## Visualization menu. %if app.config.get_bool( 'enable_tracks', False ): --- a/lib/galaxy/web/controllers/forms.py +++ b/lib/galaxy/web/controllers/forms.py @@ -60,8 +60,7 @@ class FormsGrid( grids.Grid ): grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ), ] global_actions = [ - grids.GridAction( "Create new form", dict( controller='forms', - action='new' ) ) + grids.GridAction( "Create new form", dict( controller='forms', action='create_form' ) ) ] class Forms( BaseController ): @@ -123,7 +122,7 @@ class Forms( BaseController ): @web.expose @web.require_admin - def new( self, trans, **kwd ): + def create_form( self, trans, **kwd ): params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) @@ -132,7 +131,7 @@ class Forms( BaseController ): fd, message = self.__save_form( trans, fdc_id=None, **kwd ) if not fd: return trans.response.send_redirect( web.url_for( controller='forms', - action='new', + action='create_form', message=message, status='error', name=util.restore_text( params.get( 'name', '' ) ), @@ -174,7 +173,7 @@ class Forms( BaseController ): trans.sa_session.flush() return trans.response.send_redirect( web.url_for( controller='forms', action='manage', - message='%i form(s) is deleted.' % len(id_list), + message='%i forms have been deleted.' % len(id_list), status='done') ) def __undelete( self, trans, **kwd ): id_list = util.listify( kwd['id'] ) @@ -192,7 +191,7 @@ class Forms( BaseController ): trans.sa_session.flush() return trans.response.send_redirect( web.url_for( controller='forms', action='manage', - message='%i form(s) is undeleted.' % len(id_list), + message='%i forms have been undeleted.' % len(id_list), status='done') ) @web.expose def edit( self, trans, response_redirect=None, **kwd ): @@ -470,7 +469,7 @@ class Forms( BaseController ): 'default': row[6]}) except: return trans.response.send_redirect( web.url_for( controller='forms', - action='new', + action='create_form', status='error', message='Error in importing <b>%s</b> file' % csv_file.file)) self.__imported_from_file = True @@ -501,7 +500,7 @@ class Forms( BaseController ): # validate fields for field in current_form[ 'fields' ]: if not field[ 'label' ]: - return None, "All the field label(s) must be completed." + return None, "All the field labels must be completed." # create a new form definition fd = trans.app.model.FormDefinition(name=current_form[ 'name' ], desc=current_form[ 'desc' ], --- a/templates/webapps/galaxy/admin/index.mako +++ b/templates/webapps/galaxy/admin/index.mako @@ -115,8 +115,8 @@ <div class="toolSectionBody"><div class="toolSectionBg"><div class="toolTitle"><a href="${h.url_for( controller='requests_admin', action='manage_request_types' )}" target="galaxy_main">Sequencer configurations</a></div> - <div class="toolTitle"><a href="${h.url_for( controller='requests_admin', action='list')}" target="galaxy_main">Sequencing requests</a></div> - <div class="toolTitle"><a href="${h.url_for( controller='requests_common', action='find', cntrller='requests_admin')}" target="galaxy_main">Find samples</a></div> + <div class="toolTitle"><a href="${h.url_for( controller='requests_admin', action='browse_requests' )}" target="galaxy_main">Sequencing requests</a></div> + <div class="toolTitle"><a href="${h.url_for( controller='requests_common', action='find_samples', cntrller='requests_admin' )}" target="galaxy_main">Find samples</a></div></div></div></div> --- a/templates/admin/forms/create_form.mako +++ b/templates/admin/forms/create_form.mako @@ -8,7 +8,7 @@ <div class="toolForm"><div class="toolFormTitle">Create a new form definition</div><div class="toolFormBody"> - <form name="create_form" action="${h.url_for( controller='forms', action='new', create_form=True )}" enctype="multipart/form-data" method="post" > + <form name="create_form" action="${h.url_for( controller='forms', action='create_form', create_form=True )}" enctype="multipart/form-data" method="post" > %for label, input in inputs: <div class="form-row"><label>${label}</label> --- /dev/null +++ b/templates/requests/common/edit_basic_request_info.mako @@ -0,0 +1,87 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + +<br/><br/> +<ul class="manage-table-actions"> + <li> + <a class="action-button" href="${h.url_for( controller='requests_common', action='manage_request', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Browse this request</a> + </li> + <li> + <a class="action-button" href="${h.url_for( controller=cntrller, action='browse_requests' )}">Browse all requests</a> + </li> +</ul> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">Edit sequencing request "${request.name}"</div> + <div class="toolFormBody"> + <form name="edit_basic_request_info" id="edit_basic_request_info" action="${h.url_for( controller='requests_common', action='edit_basic_request_info', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}" method="post" > + %for i, field in enumerate( widgets ): + <div class="form-row"> + <label>${field['label']}</label> + ${field['widget'].get_html()} + <div class="toolParamHelp" style="clear: both;"> + ${field['helptext']} + </div> + <div style="clear: both"></div> + </div> + %endfor + <div class="form-row"> + <input type="submit" name="edit_basic_request_info_button" value="Save"/> + </div> + </form> + </div> +</div> +<p/> +<div class="toolForm"> + <div class="toolFormTitle">Email notification settings</div> + <div class="toolFormBody"> + <form name="edit_email_settings" id="edit_email_settings" action="${h.url_for( controller='requests_common', action='edit_email_settings', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}" method="post" > + <% + email_address = '' + emails = '' + additional_email_addresses = [] + if request.notification: + for e in request.notification[ 'email' ]: + if e == request.user.email: + email_address = 'checked' + else: + additional_email_addresses.append( e ) + if additional_email_addresses: + emails = '\r\n'.join( additional_email_addresses ) + %> + <div class="form-row"> + <label>Send to:</label> + <input type="checkbox" name="email_address" value="true" ${email_address}>${request.user.email} (sequencing request owner)<input type="hidden" name="email_address" value="true"> + </div> + <div class="form-row"> + <label>Additional email addresses:</label> + <textarea name="additional_email_addresses" rows="3" cols="40">${emails}</textarea> + <div class="toolParamHelp" style="clear: both;"> + Enter one email address per line + </div> + </div> + <div class="form-row"> + <label>Select sample states to send email notification:</label> + %for sample_state in request.type.states: + <% + email_state = '' + if request.notification and sample_state.id in request.notification[ 'sample_states' ]: + email_state = 'checked' + %> + <input type="checkbox" name=sample_state_${sample_state.id} value="true" ${email_state} >${sample_state.name}<input type="hidden" name=sample_state_${sample_state.id} value="true"> + <br/> + %endfor + <div class="toolParamHelp" style="clear: both;"> + Email notification will be sent when all the samples of this sequencing request are in the selected states. + </div> + </div> + <div class="form-row"> + <input type="submit" name="edit_email_settings_button" value="Save"/> + </div> + </form> + </div> +</div> --- a/lib/galaxy/web/controllers/library_common.py +++ b/lib/galaxy/web/controllers/library_common.py @@ -1781,7 +1781,7 @@ class LibraryCommon( BaseController, Use if not forms: message = "There are no forms on which to base the template, so create a form and then add the template." return trans.response.send_redirect( web.url_for( controller='forms', - action='new', + action='create_request', message=message, status='done', form_type=trans.app.model.FormDefinition.types.LIBRARY_INFO_TEMPLATE ) ) --- a/templates/requests/common/show_request.mako +++ /dev/null @@ -1,644 +0,0 @@ -<%namespace file="/message.mako" import="render_msg" /> -<%namespace file="/requests/common/sample_state.mako" import="render_sample_state" /> -<%namespace file="/requests/common/sample_datasets.mako" import="render_sample_datasets" /> - -<%! - def inherit(context): - if context.get('use_panels'): - return '/webapps/galaxy/base_panels.mako' - else: - return '/base.mako' -%> -<%inherit file="${inherit(context)}"/> - -<%def name="stylesheets()"> - ${parent.stylesheets()} - ${h.css( "library" )} -</%def> - -<%def name="javascripts()"> - ${parent.javascripts()} - <script type="text/javascript"> - function showContent(vThis) - { - // http://www.javascriptjunkie.com - // alert(vSibling.className + " " + vDef_Key); - vParent = vThis.parentNode; - vSibling = vParent.nextSibling; - while (vSibling.nodeType==3) { - // Fix for Mozilla/FireFox Empty Space becomes a TextNode or Something - vSibling = vSibling.nextSibling; - }; - if(vSibling.style.display == "none") - { - vThis.src="/static/images/fugue/toggle.png"; - vThis.alt = "Hide"; - vSibling.style.display = "block"; - } else { - vSibling.style.display = "none"; - vThis.src="/static/images/fugue/toggle-expand.png"; - vThis.alt = "Show"; - } - return; - } - - $(document).ready(function(){ - //hide the all of the element with class msg_body - $(".msg_body").hide(); - //toggle the componenet with class msg_body - $(".msg_head").click(function(){ - $(this).next(".msg_body").slideToggle(0); - }); - }); - - // Looks for changes in sample states using an async request. Keeps - // calling itself (via setTimeout) until all samples are in a terminal - // state. - var updater = function ( sample_states ) { - // Check if there are any items left to track - var empty = true; - for ( i in sample_states ) { - empty = false; - break; - } - if ( ! empty ) { - setTimeout( function() { updater_callback( sample_states ) }, 1000 ); - } - }; - var updater_callback = function ( sample_states ) { - // Build request data - var ids = [] - var states = [] - $.each( sample_states, function ( id, state ) { - ids.push( id ); - states.push( state ); - }); - // Make ajax call - $.ajax( { - type: "POST", - url: "${h.url_for( controller='requests_common', action='sample_state_updates' )}", - dataType: "json", - data: { ids: ids.join( "," ), states: states.join( "," ) }, - success : function ( data ) { - $.each( data, function( id, val, cntrller ) { - // Replace HTML - var cell1 = $("#sampleState-" + id); - cell1.html( val.html_state ); - var cell2 = $("#sampleDatasets-" + id); - cell2.html( val.html_datasets ); - sample_states[ parseInt(id) ] = val.state; - }); - updater( sample_states ); - }, - error: function() { - // Just retry, like the old method, should try to be smarter - updater( sample_states ); - } - }); - }; - - function checkAllFields() - { - var chkAll = document.getElementById('checkAll'); - var checks = document.getElementsByTagName('input'); - var boxLength = checks.length; - var allChecked = false; - var totalChecked = 0; - if ( chkAll.checked == true ) - { - for ( i=0; i < boxLength; i++ ) - { - if ( checks[i].name.indexOf( 'select_sample_' ) != -1) - { - checks[i].checked = true; - } - } - } - else - { - for ( i=0; i < boxLength; i++ ) - { - if ( checks[i].name.indexOf( 'select_sample_' ) != -1) - { - checks[i].checked = false - } - } - } - } - - function stopRKey(evt) { - var evt = (evt) ? evt : ((event) ? event : null); - var node = (evt.target) ? evt.target : ((evt.srcElement) ? evt.srcElement : null); - if ((evt.keyCode == 13) && (node.type=="text")) {return false;} - } - document.onkeypress = stopRKey - </script> -</%def> - -<% samples_not_ready = request.sequence_run_ready() %> -%if samples_not_ready: - ${render_msg( "Select a target data library and folder for all the samples before starting the sequence run", "warning" )} -%endif - -%if request.rejected(): - ${render_msg( "Reason for rejection: "+request.last_comment(), "warning" )} -%endif - -%if message: - ${render_msg( message, status )} -%endif - -<div class="grid-header"> - <h2>Sequencing Request "${request.name}"</h2> - <div class="toolParamHelp" style="clear: both;"> - <b>Sequencer</b>: ${request.type.name} - %if cntrller == 'requests_admin': - | <b>User</b>: ${request.user.email} - %endif - %if request.state() == request.states.SUBMITTED: - | <b>State</b>: <i>${request.state()}</i> - %else: - | <b>State</b>: ${request.state()} - %endif - </div> -</div> - -<br/><br/> - -<ul class="manage-table-actions"> - <li><a class="action-button" id="seqreq-${request.id}-popup" class="menubutton">Sequencing Request Actions</a></li> - <div popupmenu="seqreq-${request.id}-popup"> - %if request.unsubmitted() and request.samples: - <a class="action-button" confirm="More samples cannot be added to this request once it is submitted. Click OK to submit." href="${h.url_for( controller=cntrller, action='list', operation='Submit', id=trans.security.encode_id(request.id) )}">Submit</a> - %endif - <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='events', id=trans.security.encode_id(request.id) )}">History</a> - <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='Edit', id=trans.security.encode_id(request.id))}">Edit</a> - %if cntrller == 'requests_admin' and trans.user_is_admin(): - %if request.submitted(): - <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='reject', id=trans.security.encode_id(request.id))}">Reject</a> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='get_data', show_page=True, request_id=request.id)}">Select dataset(s) to transfer</a> - %endif - %endif - </div> - <li><a class="action-button" href="${h.url_for( controller=cntrller, action='list')}">Browse requests</a></li> -</ul> - -<h4><img src="/static/images/fugue/toggle-expand.png" alt="Show" onclick="showContent(this);" style="cursor:pointer;"/> Request Information</h4> -<div style="display:none;" > - <table class="grid" border="0"> - <tbody> - <tr> - <td valign="top" width="50%"> - <div class="form-row"> - <label>Description:</label> - %if request.desc: - ${request.desc} - %else: - <i>None</i> - %endif - </div> - <div style="clear: both"></div> - %for index, rd in enumerate(request_details): - <div class="form-row"> - <label>${rd['label']}:</label> - %if not rd['value']: - <i>None</i> - %else: - %if rd['label'] == 'State': - <a href="${h.url_for( controller=cntrller, action='list', operation='events', id=trans.security.encode_id(request.id) )}">${rd['value']}</a> - %else: - ${rd['value']} - %endif - %endif - </div> - <div style="clear: both"></div> - %endfor - </td> - <td valign="top" width="50%"> - <div class="form-row"> - <label>Date created:</label> - ${request.create_time} - </div> - <div class="form-row"> - <label>Date updated:</label> - ${request.update_time} - </div> - <div class="form-row"> - <label>Email notification recipient(s):</label> - <% emails = ', '.join(request.notification['email']) %> - %if emails: - ${emails} - %else: - <i>None</i> - %endif - </div> - <div style="clear: both"></div> - <div class="form-row"> - <label>Email notification on sample state(s):</label> - <% - states = [] - for ss in request.type.states: - if ss.id in request.notification['sample_states']: - states.append(ss.name) - states = ', '.join(states) - %> - %if states: - ${states} - %else: - <i>None</i> - %endif - </div> - <div style="clear: both"></div> - </td> - </tr> - </tbody> - </table> - <div class="form-row"> - <ul class="manage-table-actions"> - <li><a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='Edit', id=trans.security.encode_id(request.id))}">Edit request information</a></li> - </ul> - </div> -</div> -<br/> -<form id="show_request" name="show_request" action="${h.url_for( controller='requests_common', cntrller=cntrller, action='request_page', edit_mode=edit_mode )}" method="post" > - <input type="hidden" name="id" value="${trans.security.encode_id(request.id)}" /> - %if current_samples: - ## first render the basic info grid - ${render_basic_info_grid()} - %if not request.new() and edit_mode == 'False' and len(sample_ops.options) > 1: - <div class="form-row" style="background-color:#FAFAFA;"> - For selected sample(s): - ${sample_ops.get_html()} - </div> - %if 'none' not in sample_ops.get_selected( return_label=True, return_value=True ) and len(selected_samples): - <div class="form-row" style="background-color:#FAFAFA;"> - %if trans.app.model.Sample.bulk_operations.CHANGE_STATE in sample_ops.get_selected( return_label=True, return_value=True ): - <% - widgets, title = request.type.change_state_widgets(trans) - %> - %for w in widgets: - <div class="form-row"> - <label> - ${w[0]}: - </label> - ${w[1].get_html()} - %if w[0] == 'Comments': - <div class="toolParamHelp" style="clear: both;"> - Optional - </div> - %endif - </div> - %endfor - <div class="form-row"> - <input type="submit" name="change_state_button" value="Save"/> - <input type="submit" name="change_state_button" value="Cancel"/> - </div> - %elif trans.app.model.Sample.bulk_operations.SELECT_LIBRARY in sample_ops.get_selected( return_label=True, return_value=True ): - <div class="form-row"> - <label>Select data library:</label> - ${bulk_lib_ops[0].get_html()} - </div> - %if not 'none' in bulk_lib_ops[0].get_selected( return_label=True, return_value=True ): - <div class="form-row"> - <label>Select folder:</label> - ${bulk_lib_ops[1].get_html()} - </div> - <div class="form-row"> - <input type="submit" name="change_lib_button" value="Save"/> - <input type="submit" name="change_lib_button" value="Cancel"/> - </div> - %endif - %endif - </div> - %endif - %endif - ## then render the other grid(s) - <% trans.sa_session.refresh( request.type.sample_form ) %> - %for grid_index, grid_name in enumerate(request.type.sample_form.layout): - ${render_grid( grid_index, grid_name, request.type.sample_form.fields_of_grid( grid_index ) )} - %endfor - %else: - <label>There are no samples.</label> - %endif - %if request.samples and request.submitted(): - <script type="text/javascript"> - // Updater - updater({${ ",".join( [ '"%s" : "%s"' % ( s.id, s.current_state().name ) for s in request.samples ] ) }}); - </script> - %endif - %if edit_mode == 'False': - <table class="grid"> - <tbody> - <tr> - <div class="form-row"> - %if request.unsubmitted(): - <td> - %if current_samples: - <label>Copy </label> - <input type="integer" name="num_sample_to_copy" value="1" size="3"/> - <label>sample(s) from sample</label> - ${sample_copy.get_html()} - %endif - <input type="submit" name="add_sample_button" value="Add New"/> - </td> - %endif - <td> - %if len(current_samples) and len(current_samples) <= len(request.samples): - <input type="submit" name="edit_samples_button" value="Edit samples"/> - %endif - </td> - </div> - </tr> - </tbody> - </table> - %endif - %if request.samples or current_samples: - <div class="form-row"> - <div style="float: left; width: 250px; margin-right: 10px;"> - <input type="hidden" name="refresh" value="true" size="40"/> - </div> - <div style="clear: both"></div> - </div> - %if edit_mode == 'True': - <div class="form-row"> - <input type="submit" name="save_samples_button" value="Save"/> - <input type="submit" name="cancel_changes_button" value="Cancel"/> - </div> - %elif edit_mode == 'True' or len(current_samples) > len(request.samples): - <div class="form-row"> - <input type="submit" name="save_samples_button" value="Save"/> - <input type="submit" name="cancel_changes_button" value="Cancel"/> - </div> - %endif - %endif -</form> -<br/> -%if request.unsubmitted(): - <form id="import" name="import" action="${h.url_for( controller='requests_common', action='request_page', edit_mode=edit_mode, request_id=trans.security.encode_id(request.id) )}" enctype="multipart/form-data" method="post" > - <h4><img src="/static/images/fugue/toggle-expand.png" alt="Show" onclick="showContent(this);" style="cursor:pointer;"/> Import samples</h4> - <div style="display:none;"> - <input type="file" name="file_data" /> - <input type="submit" name="import_samples_button" value="Import samples"/> - <br/> - <div class="toolParamHelp" style="clear: both;"> - The csv file must be in the following format:<br/> - SampleName,DataLibrary,DataLibraryFolder,FieldValue1,FieldValue2... - </div> - </div> - </form> -%endif - -<%def name="render_grid( grid_index, grid_name, fields_dict )"> - <br/> - <% if not grid_name: - grid_name = "Grid "+ grid_index - %> - <div> - %if edit_mode == 'True' or len(current_samples) > len(request.samples): - <h4><img src="/static/images/fugue/toggle.png" alt="Show" onclick="showContent(this);" style="cursor:pointer;"/> ${grid_name}</h4> - <div> - %else: - <h4><img src="/static/images/fugue/toggle-expand.png" alt="Hide" onclick="showContent(this);" style="cursor:pointer;"/> ${grid_name}</h4> - <div style="display:none;"> - %endif - <table class="grid"> - <thead> - <tr> - <th>Name</th> - %for index, field in fields_dict.items(): - <th> - ${field['label']} - <div class="toolParamHelp" style="clear: both;"> - <i>${field['helptext']}</i> - </div> - </th> - %endfor - <th></th> - </tr> - <thead> - <tbody> - <% - trans.sa_session.refresh( request ) - %> - %for sample_index, sample in enumerate(current_samples): - %if edit_mode == 'True': - <tr> - ${render_sample_form( sample_index, sample['name'], sample['field_values'], fields_dict)} - </tr> - %else: - <tr> - %if sample_index in range(len(request.samples)): - ${render_sample( sample_index, sample['name'], sample['field_values'], fields_dict )} - %else: - ${render_sample_form( sample_index, sample['name'], sample['field_values'], fields_dict)} - %endif - </tr> - %endif - %endfor - </tbody> - </table> - </div> - </div> -</%def> - -## This function displays the "Basic Information" grid -<%def name="render_basic_info_grid()"> - <h3>Sample Information</h3> - <table class="grid"> - <thead> - <tr> - <th><input type="checkbox" id="checkAll" name=select_all_samples value="true" onclick='checkAllFields(1);'><input type="hidden" name=select_all_samples value="true"></th> - <th>Name</th> - <th>Barcode</th> - <th>State</th> - <th>Data Library</th> - <th>Folder</th> - %if request.submitted() or request.complete(): - <th>Dataset(s) Transferred</th> - %endif - <th></th> - </tr> - <thead> - <tbody> - <% trans.sa_session.refresh( request ) %> - %for sample_index, info in enumerate( current_samples ): - <% - if sample_index in range(len(request.samples)): - sample = request.samples[sample_index] - else: - sample = None - %> - %if edit_mode == 'True': - <tr> - ${show_basic_info_form( sample_index, sample, info )} - </tr> - %else: - <tr> - %if sample_index in range(len(request.samples)): - %if sample.id in selected_samples: - <td><input type="checkbox" name=select_sample_${sample.id} id="sample_checkbox" value="true" checked><input type="hidden" name=select_sample_${sample.id} id="sample_checkbox" value="true"></td> - %else: - <td><input type="checkbox" name=select_sample_${sample.id} id="sample_checkbox" value="true"><input type="hidden" name=select_sample_${sample.id} id="sample_checkbox" value="true"></td> - %endif - <td>${info['name']}</td> - <td>${info['barcode']}</td> - %if sample.request.unsubmitted(): - <td>Unsubmitted</td> - %else: - <td id="sampleState-${sample.id}">${render_sample_state( cntrller, sample )}</td> - %endif - %if info['library']: - %if cntrller == 'requests': - <td><a href="${h.url_for( controller='library_common', action='browse_library', cntrller='library', id=trans.security.encode_id( info['library'].id ) )}">${info['library'].name}</a></td> - %elif cntrller == 'requests_admin': - <td><a href="${h.url_for( controller='library_common', action='browse_library', cntrller='library_admin', id=trans.security.encode_id( info['library'].id ) )}">${info['library'].name}</a></td> - %endif - %else: - <td></td> - %endif - %if info['folder']: - <td>${info['folder'].name}</td> - %else: - <td></td> - %endif - %if request.submitted() or request.complete(): - <td id="sampleDatasets-${sample.id}"> - ${render_sample_datasets( cntrller, sample )} - </td> - %endif - %else: - ${show_basic_info_form( sample_index, sample, info )} - %endif - %if request.unsubmitted() or request.rejected(): - <td> - %if sample: - %if sample.request.unsubmitted(): - <a class="action-button" href="${h.url_for( controller='requests_common', cntrller=cntrller, action='delete_sample', request_id=request.id, sample_id=sample_index )}"> - <img src="${h.url_for('/static/images/delete_icon.png')}" style="cursor:pointer;"/> - <span></span></a> - %endif - %endif - </td> - %endif - </tr> - %endif - %endfor - </tbody> - </table> -</%def> - -<%def name="show_basic_info_form( sample_index, sample, info )"> - <td></td> - <td> - <input type="text" name="sample_${sample_index}_name" value="${info['name']}" size="10"/> - <div class="toolParamHelp" style="clear: both;"> - <i>${' (required)' }</i> - </div> - </td> - %if cntrller == 'requests': - %if sample: - %if sample.request.unsubmitted(): - <td></td> - %else: - <td><input type="text" name="sample_${sample_index}_barcode" value="${info['barcode']}" size="10"/></td> - %endif - %else: - <td></td> - %endif - %elif cntrller == 'requests_admin': - %if sample: - %if sample.request.unsubmitted(): - <td></td> - %else: - <td><input type="text" name="sample_${sample_index}_barcode" value="${info['barcode']}" size="10"/></td> - %endif - %else: - <td></td> - %endif - %endif - %if sample: - %if sample.request.unsubmitted(): - <td>Unsubmitted</td> - %else: - <td><a href="${h.url_for( controller='requests_admin', action='sample_events', sample_id=sample.id)}">${sample.current_state().name}</a></td> - %endif - %else: - <td></td> - %endif - <td>${info['lib_widget'].get_html()}</td> - <td>${info['folder_widget'].get_html()}</td> - %if request.submitted() or request.complete(): - %if sample: - <td><a href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">${len(sample.datasets)}</a></td> - %else: - <td><a href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">Add</a></td> - %endif - %endif -</%def> - -<%def name="render_sample( index, sample_name, sample_values, fields_dict )"> - <td> - ${sample_name} - </td> - %for field_index, field in fields_dict.items(): - <td> - %if sample_values[field_index]: - %if field['type'] == 'WorkflowField': - %if str(sample_values[field_index]) != 'none': - <% workflow = trans.sa_session.query( trans.app.model.StoredWorkflow ).get( int(sample_values[field_index]) ) %> - <a href="${h.url_for( controller='workflow', action='run', id=trans.security.encode_id(workflow.id) )}">${workflow.name}</a> - %endif - %else: - ${sample_values[field_index]} - %endif - %else: - <i>None</i> - %endif - </td> - %endfor -</%def> - -<%def name="render_sample_form( index, sample_name, sample_values, fields_dict )"> - <td> - ${sample_name} - </td> - %for field_index, field in fields_dict.items(): - <td> - %if field['type'] == 'TextField': - <input type="text" name="sample_${index}_field_${field_index}" value="${sample_values[field_index]}" size="7"/> - %elif field['type'] == 'SelectField': - <select name="sample_${index}_field_${field_index}" last_selected_value="2"> - %for option_index, option in enumerate(field['selectlist']): - %if option == sample_values[field_index]: - <option value="${option}" selected>${option}</option> - %else: - <option value="${option}">${option}</option> - %endif - %endfor - </select> - %elif field['type'] == 'WorkflowField': - <select name="sample_${index}_field_${field_index}"> - %if str(sample_values[field_index]) == 'none': - <option value="none" selected>Select one</option> - %else: - <option value="none">Select one</option> - %endif - %for option_index, option in enumerate(request.user.stored_workflows): - %if not option.deleted: - %if str(option.id) == str(sample_values[field_index]): - <option value="${option.id}" selected>${option.name}</option> - %else: - <option value="${option.id}">${option.name}</option> - %endif - %endif - %endfor - </select> - %elif field['type'] == 'CheckboxField': - <input type="checkbox" name="sample_${index}_field_${field_index}" value="Yes"/> - %endif - <div class="toolParamHelp" style="clear: both;"> - <i>${'('+field['required']+')' }</i> - </div> - </td> - %endfor -</%def> --- a/templates/requests/common/sample_datasets.mako +++ b/templates/requests/common/sample_datasets.mako @@ -1,7 +1,5 @@ <%def name="render_sample_datasets( cntrller, sample )"> - <a href="${h.url_for(controller='requests_common', cntrller=cntrller, action='show_datatx_page', sample_id=trans.security.encode_id(sample.id))}">${sample.transferred_dataset_files()}/${len(sample.datasets)}</a> + <a href="${h.url_for( controller='requests_common', action='view_dataset_transfer', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${sample.transferred_dataset_files} / ${len( sample.datasets )}</a></%def> - - ${render_sample_datasets( cntrller, sample )} --- a/test/base/twilltestcase.py +++ b/test/base/twilltestcase.py @@ -1320,7 +1320,7 @@ class TwillTestCase( unittest.TestCase ) def create_form( self, name, desc, form_type, field_type='TextField', form_layout_name='', num_fields=1, num_options=0, strings_displayed=[], strings_displayed_after_submit=[] ): """Create a new form definition.""" - self.visit_url( "%s/forms/new" % self.url ) + self.visit_url( "%s/forms/create_form" % self.url ) for check_str in strings_displayed: self.check_page_for_string( check_str ) tc.fv( "1", "name", name ) @@ -1405,13 +1405,13 @@ class TwillTestCase( unittest.TestCase ) self.home() url = "%s/forms/manage?operation=delete&id=%s" % ( self.url, form_id ) self.visit_url( url ) - check_str = "1 form(s) is deleted." + check_str = "1 forms have been deleted." self.check_page_for_string( check_str ) self.home() # Requests stuff def check_request_grid( self, cntrller, state, deleted=False, strings_displayed=[] ): - self.visit_url( '%s/%s/list?sort=create_time&f-state=%s&f-deleted=%s' % \ + self.visit_url( '%s/%s/browse_requests?sort=create_time&f-state=%s&f-deleted=%s' % \ ( self.url, cntrller, state.replace( ' ', '+' ), str( deleted ) ) ) for check_str in strings_displayed: self.check_page_for_string( check_str ) @@ -1445,17 +1445,16 @@ class TwillTestCase( unittest.TestCase ) check_str = "Permissions updated for sequencer configuration '%s'" % request_type_name self.check_page_for_string( check_str ) self.home() - def create_request( self, cntrller, request_type_id, name, desc, field_value_tuples, select_user_id='', - refresh='False', strings_displayed=[], strings_displayed_after_submit=[] ): - self.visit_url( "%s/requests_common/new?cntrller=%s&refresh=%s&select_request_type=True" % ( self.url, cntrller, refresh ) ) - # The select_request_type SelectList requires a refresh_on_change - self.refresh_form( 'select_request_type', request_type_id ) - if cntrller == 'requests_admin' and select_user_id: + def create_request( self, cntrller, request_type_id, name, desc, field_value_tuples, other_users_id='', + strings_displayed=[], strings_displayed_after_submit=[] ): + self.visit_url( "%s/requests_common/create_request?cntrller=%s" % ( self.url, cntrller ) ) + # The request_type SelectList requires a refresh_on_change + self.refresh_form( 'request_type_id', request_type_id ) + if cntrller == 'requests_admin' and other_users_id: # The admin is creating a request on behalf of another user - # The select_user SelectList requires a refresh_on_change - # gvk - 9/22/10: TODO: why does select_user require a refresh_on_change? Nothing in the - # code is apparent as to why this is done. - self.refresh_form( 'select_user', select_user_id ) + # The user_id SelectField requires a refresh_on_change so that the selected + # user's addresses will be populated in the AddressField widget + self.refresh_form( "user_id", other_users_id ) for check_str in strings_displayed: self.check_page_for_string( check_str ) tc.fv( "1", "name", name ) @@ -1469,15 +1468,14 @@ class TwillTestCase( unittest.TestCase ) # user_address to be selected. self.refresh_form( field_name, field_value ) else: - data = self.last_page() - file( 'greg.html', 'wb' ).write(data ) tc.fv( "1", field_name, field_value ) tc.submit( "create_request_button" ) for check_str in strings_displayed_after_submit: self.check_page_for_string( check_str ) self.home() - def edit_request( self, request_id, name, new_name='', new_desc='', new_fields=[], strings_displayed=[], strings_displayed_after_submit=[] ): - self.visit_url( "%s/requests/list?operation=Edit&id=%s" % ( self.url, request_id ) ) + def edit_basic_request_info( self, cntrller, request_id, name, new_name='', new_desc='', new_fields=[], + strings_displayed=[], strings_displayed_after_submit=[] ): + self.visit_url( "%s/requests_common/edit_basic_request_info?cntrller=%s&id=%s" % ( self.url, cntrller, request_id ) ) for check_str in strings_displayed: self.check_page_for_string( check_str ) self.check_page_for_string( 'Edit sequencing request "%s"' % name ) @@ -1487,21 +1485,21 @@ class TwillTestCase( unittest.TestCase ) tc.fv( "1", "desc", new_desc ) for index, field_value in enumerate( new_fields ): tc.fv( "1", "field_%i" % index, field_value ) - tc.submit( "save_changes_request_button" ) + tc.submit( "edit_basic_request_info_button" ) for check_str in strings_displayed_after_submit: self.check_page_for_string( check_str ) def add_samples( self, cntrller, request_id, request_name, sample_value_tuples, strings_displayed=[], strings_displayed_after_submit=[] ): - self.visit_url( "%s/requests/list?operation=show&id=%s" % ( self.url, request_id ) ) + self.visit_url( "%s/requests_common/manage_request?cntrller=%s&id=%s" % ( self.url, cntrller, request_id ) ) for check_str in strings_displayed: self.check_page_for_string( check_str ) # Simulate clicking the add-sample_button on the form. (gvk: 9/21/10 - TODO : There must be a bug in the mako template # because twill cannot find any forms on the page, but I cannot find it although I've spent time cleaning up the # template code and looking for any problems. - url = "%s/requests_common/request_page?cntrller=%s&edit_mode=False&id=%s" % ( self.url, cntrller, request_id ) + url = "%s/requests_common/manage_request?cntrller=%s&id=%s" % ( self.url, cntrller, request_id ) # This should work, but although twill does not thorw any exceptions, the button click never occurs - # There are multiple forms on this page, and we'll only be using the form named show_request. + # There are multiple forms on this page, and we'll only be using the form named manage_request. # for sample_index, sample_value_tuple in enumerate( sample_value_tuples ): - # # Add the following form value to the already populated hidden field so that the show_request + # # Add the following form value to the already populated hidden field so that the manage_request # # form is the current form # tc.fv( "1", "id", request_id ) # tc.submit( 'add_sample_button' ) @@ -1528,11 +1526,11 @@ class TwillTestCase( unittest.TestCase ) for check_str in strings_displayed_after_submit: self.check_page_for_string( check_str ) def submit_request( self, cntrller, request_id, request_name, strings_displayed_after_submit=[] ): - self.visit_url( "%s/%s/list?operation=Submit&id=%s" % ( self.url, cntrller, request_id ) ) + self.visit_url( "%s/requests_common/submit_request?cntrller=%s&id=%s" % ( self.url, cntrller, request_id ) ) for check_str in strings_displayed_after_submit: self.check_page_for_string( check_str ) def reject_request( self, request_id, request_name, comment, strings_displayed=[], strings_displayed_after_submit=[] ): - self.visit_url( "%s/requests_admin/list?operation=Reject&id=%s" % ( self.url, request_id ) ) + self.visit_url( "%s/requests_admin/reject?id=%s" % ( self.url, request_id ) ) for check_str in strings_displayed: self.check_page_for_string( check_str ) tc.fv( "1", "comment", comment ) @@ -1542,7 +1540,7 @@ class TwillTestCase( unittest.TestCase ) def add_bar_codes( self, request_id, request_name, bar_codes, samples, strings_displayed_after_submit=[] ): # We have to simulate the form submission here since twill barfs on the page # gvk - 9/22/10 - TODO: make sure the mako template produces valid html - url = "%s/requests_common/request_page?cntrller=requests_admin&edit_mode=True&id=%s" % ( self.url, request_id ) + url = "%s/requests_common/manage_request?cntrller=requests_admin&id=%s&edit_samples_button=Edit+samples" % ( self.url, request_id ) for index, field_value in enumerate( bar_codes ): sample_field_name = "sample_%i_name" % index sample_field_value = samples[ index ].name.replace( ' ', '+' ) @@ -1553,18 +1551,18 @@ class TwillTestCase( unittest.TestCase ) self.visit_url( url ) for check_str in strings_displayed_after_submit: self.check_page_for_string( check_str ) - def change_sample_state( self, request_id, request_name, sample_name, sample_id, new_state_id, new_state_name, comment='', + def change_sample_state( self, request_id, request_name, sample_name, sample_id, new_sample_state_id, new_state_name, comment='', strings_displayed=[], strings_displayed_after_submit=[] ): # We have to simulate the form submission here since twill barfs on the page # gvk - 9/22/10 - TODO: make sure the mako template produces valid html - url = "%s/requests_common/request_page?cntrller=requests_admin&edit_mode=False&id=%s" % ( self.url, request_id ) + url = "%s/requests_common/manage_request?cntrller=requests_admin&id=%s" % ( self.url, request_id ) # select_sample_%i=true must be included twice to simulate a CheckboxField checked setting. - url += "&comment=%s&select_sample_%i=true&select_sample_%i=true&select_state=%i" % ( comment, sample_id, sample_id, new_state_id ) - url += "&select_sample_operation=Change%20state&refresh=true" + url += "&comment=%s&select_sample_%i=true&select_sample_%i=true&sample_state_id=%i" % ( comment, sample_id, sample_id, new_sample_state_id ) + url += "&sample_operation=Change%20state&refresh=true" url += "&change_state_button=Save" self.visit_url( url ) self.check_page_for_string( 'Sequencing Request "%s"' % request_name ) - self.visit_url( "%s/requests_common/sample_events?cntrller=requests_admin&sample_id=%i" % (self.url, sample_id) ) + self.visit_url( "%s/requests_common/sample_events?cntrller=requests_admin&sample_id=%i" % ( self.url, sample_id ) ) self.check_page_for_string( 'Events for Sample "%s"' % sample_name ) self.check_page_for_string( new_state_name ) def add_user_address( self, user_id, address_dict ): --- a/lib/galaxy/web/controllers/requests_common.py +++ b/lib/galaxy/web/controllers/requests_common.py @@ -1,23 +1,112 @@ from galaxy.web.base.controller import * from galaxy.web.framework.helpers import time_ago, iff, grids from galaxy.model.orm import * -from galaxy.datatypes import sniff from galaxy import model, util -from galaxy.util.streamball import StreamBall -import logging, tempfile, zipfile, tarfile, os, sys, subprocess -from galaxy.web.form_builder import * -from datetime import datetime, timedelta -from sqlalchemy.sql.expression import func, and_ -from sqlalchemy.sql import select -import pexpect -import ConfigParser, threading, time -from amqplib import client_0_8 as amqp -import csv, smtplib, socket +from galaxy.web.form_builder import * +import logging, os, csv + log = logging.getLogger( __name__ ) +class RequestsGrid( grids.Grid ): + # Custom column types + class NameColumn( grids.TextColumn ): + def get_value( self, trans, grid, request ): + return request.name + class DescriptionColumn( grids.TextColumn ): + def get_value(self, trans, grid, request): + return request.desc + class SamplesColumn( grids.GridColumn ): + def get_value(self, trans, grid, request): + return str( len( request.samples ) ) + class TypeColumn( grids.TextColumn ): + def get_value( self, trans, grid, request ): + return request.type.name + class StateColumn( grids.StateColumn ): + def get_value(self, trans, grid, request ): + state = request.state + if state == request.states.REJECTED: + state_color = 'error' + elif state == request.states.NEW: + state_color = 'new' + elif state == request.states.SUBMITTED: + state_color = 'running' + elif state == request.states.COMPLETE: + state_color = 'ok' + else: + state_color = state + return '<div class="count-box state-color-%s">%s</div>' % ( state_color, state ) + def filter( self, trans, user, query, column_filter ): + """ Modify query to filter request by state. """ + if column_filter == "All": + return query + if column_filter: + return query.join( model.RequestEvent.table ) \ + .filter( self.model_class.table.c.id == model.RequestEvent.table.c.request_id ) \ + .filter( model.RequestEvent.table.c.state == column_filter ) \ + .filter( model.RequestEvent.table.c.id.in_( select( columns=[ func.max( model.RequestEvent.table.c.id ) ], + from_obj=model.RequestEvent.table, + group_by=model.RequestEvent.table.c.request_id ) ) ) + def get_accepted_filters( self ): + """ Returns a list of accepted filters for this column. """ + # TODO: is this method necessary? + accepted_filter_labels_and_vals = [ model.Request.states.get( state ) for state in model.Request.states ] + accepted_filter_labels_and_vals.append( "All" ) + accepted_filters = [] + for val in accepted_filter_labels_and_vals: + label = val.lower() + args = { self.key: val } + accepted_filters.append( grids.GridColumnFilter( label, args ) ) + return accepted_filters + + # Grid definition + title = "Sequencing Requests" + template = "requests/grid.mako" + model_class = model.Request + default_sort_key = "-update_time" + num_rows_per_page = 50 + preserve_state = True + use_paging = True + default_filter = dict( state="All", deleted="False" ) + columns = [ + NameColumn( "Name", + key="name", + link=( lambda item: iff( item.deleted, None, dict( operation="manage_request", id=item.id ) ) ), + attach_popup=True, + filterable="advanced" ), + DescriptionColumn( "Description", + key='desc', + filterable="advanced" ), + SamplesColumn( "Samples", + link=( lambda item: iff( item.deleted, None, dict( operation="manage_request", id=item.id ) ) ) ), + TypeColumn( "Sequencer", + link=( lambda item: iff( item.deleted, None, dict( operation="view_type", id=item.type.id ) ) ) ), + grids.GridColumn( "Last Updated", key="update_time", format=time_ago ), + grids.DeletedColumn( "Deleted", + key="deleted", + visible=False, + filterable="advanced" ), + StateColumn( "State", + key='state', + filterable="advanced", + link=( lambda item: iff( item.deleted, None, dict( operation="events", id=item.id ) ) ) + ) + ] + columns.append( grids.MulticolFilterColumn( "Search", + cols_to_filter=[ columns[0], columns[1], columns[6] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) + operations = [ + grids.GridOperation( "Submit", + allow_multiple=False, + condition=( lambda item: not item.deleted and item.is_unsubmitted and item.samples ), + confirm="Samples cannot be added to this request after it is submitted. Click OK to submit." ) + ] + class RequestsCommon( BaseController, UsesFormDefinitionWidgets ): @web.json - def sample_state_updates( self, trans, ids=None, states=None, cntrller=None ): + def sample_state_updates( self, trans, cntrller, ids=None, states=None ): + pass # Avoid caching trans.response.headers['Pragma'] = 'no-cache' trans.response.headers['Expires'] = '0' @@ -28,207 +117,186 @@ class RequestsCommon( BaseController, Us states = states.split( "," ) for id, state in zip( ids, states ): sample = trans.sa_session.query( self.app.model.Sample ).get( id ) - if sample.current_state().name != state: - rval[id] = { - "state": sample.current_state().name, - "datasets": len(sample.datasets), - "html_state": unicode( trans.fill_template( "requests/common/sample_state.mako", sample=sample, cntrller=cntrller ), 'utf-8' ), - "html_datasets": unicode( trans.fill_template( "requests/common/sample_datasets.mako", trans=trans, sample=sample, cntrller=cntrller ), 'utf-8' ) - } + if sample.state.name != state: + rval[id] = { "state": sample.state.name, + "datasets": len( sample.datasets ), + "html_state": unicode( trans.fill_template( "requests/common/sample_state.mako", + sample=sample, + cntrller=cntrller ), + 'utf-8' ), + "html_datasets": unicode( trans.fill_template( "requests/common/sample_datasets.mako", + sample=sample, + cntrller=cntrller ), + 'utf-8' ) } return rval @web.expose - @web.require_login( "create/submit sequencing requests" ) - def new(self, trans, **kwd): + @web.require_login( "create sequencing requests" ) + def create_request( self, trans, cntrller, **kwd ): params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) + is_admin = cntrller == 'requests_admin' and trans.user_is_admin() + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + request_type_id = params.get( 'request_type_id', 'none' ) + if request_type_id != 'none': + request_type = trans.sa_session.query( trans.model.RequestType ).get( trans.security.decode_id( request_type_id ) ) + else: + request_type = None + # user_id will not be 'none' if an admin user is submitting this request on behalf of another user + # and they selected that user's id from the user_id SelectField. + user_id = params.get( 'user_id', 'none' ) + if user_id != 'none': + user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) ) + elif not is_admin: + user = trans.user + else: + user = None + if params.get( 'create_request_button', False ) or params.get( 'add_sample_button', False ): + name = util.restore_text( params.get( 'name', '' ) ) + if is_admin and user_id == 'none': + message = 'Select the user on behalf of whom you are submitting this request.' + status = 'error' + elif not name: + message = 'Enter the name of the request.' + status = 'error' + else: + request = self.__save_request( trans, cntrller, **kwd ) + message = 'The request has been created.' + if params.get( 'create_request_button', False ): + return trans.response.send_redirect( web.url_for( controller=cntrller, + action='browse_requests', + message=message , + status='done' ) ) + elif params.get( 'add_sample_button', False ): + return self.__add_sample( trans, cntrller, request, **kwd ) + request_type_select_field = self.__build_request_type_id_select_field( trans, selected_value=request_type_id ) + # Widgets to be rendered on the request form + widgets = [] + if request_type is not None or status == 'error': + # Either the user selected a request_type or an error exists on the form. + if is_admin: + widgets.append( dict( label='Select user', + widget=self.__build_user_id_select_field( trans, selected_value=user_id ), + helptext='Submit the request on behalf of the selected user (Required)')) + widgets.append( dict( label='Name of the Experiment', + widget=TextField( 'name', 40, util.restore_text( params.get( 'name', '' ) ) ), + helptext='(Required)') ) + widgets.append( dict( label='Description', + widget=TextField( 'desc', 40, util.restore_text( params.get( 'desc', '' ) )), + helptext='(Optional)') ) + if request_type is not None: + widgets += request_type.request_form.get_widgets( user, **kwd ) + # In case there is an error on the form, make sure to populate widget fields with anything the user + # may have already entered. + self.populate_widgets_from_kwd( trans, widgets, **kwd ) + return trans.fill_template( '/requests/common/create_request.mako', + cntrller=cntrller, + request_type_select_field=request_type_select_field, + request_type_select_field_selected=request_type_id, + widgets=widgets, + message=message, + status=status ) + @web.expose + @web.require_login( "edit sequencing requests" ) + def edit_basic_request_info( self, trans, cntrller, **kwd ): + params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) - if params.get('select_request_type', False) == 'True': - return trans.fill_template( '/requests/common/new_request.mako', - cntrller=cntrller, - select_request_type=self.__select_request_type(trans, 'none'), - widgets=[], - message=message, - status=status) - elif params.get('create_request_button', False) == 'Save' \ - or params.get('create_request_samples_button', False) == 'Add samples': - request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) ) - if not util.restore_text(params.get('name', '')) \ - or util.restore_text(params.get('select_user', '')) == unicode('none'): - message = 'Please enter the <b>Name</b> of the request and the <b>user</b> on behalf of whom this request will be submitted before saving this request' - kwd['create'] = 'True' - kwd['status'] = 'error' - kwd['message'] = message - kwd['create_request_button'] = None - kwd['create_request_samples_button'] = None - return trans.response.send_redirect( web.url_for( controller='requests_common', - cntrller=cntrller, - action='new', - **kwd) ) - request = self.__save_request(trans, None, **kwd) - message = 'The new request named <b>%s</b> has been created' % request.name - if params.get('create_request_button', False) == 'Save': - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - message=message , - status='done') ) - elif params.get('create_request_samples_button', False) == 'Add samples': - new_kwd = {} - new_kwd['id'] = trans.security.encode_id(request.id) - new_kwd['operation'] = 'show' - new_kwd['add_sample'] = True - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - message=message , - status='done', - **new_kwd) ) - elif params.get('refresh', False) == 'true': - return self.__show_request_form(trans, **kwd) - def __select_request_type(self, trans, rtid): - requesttype_list = trans.user.accessible_request_types(trans) - rt_ids = ['none'] - for rt in requesttype_list: - if not rt.deleted: - rt_ids.append(str(rt.id)) - select_reqtype = SelectField('select_request_type', - refresh_on_change=True, - refresh_on_change_values=rt_ids[1:]) - if rtid == 'none': - select_reqtype.add_option('Select one', 'none', selected=True) - else: - select_reqtype.add_option('Select one', 'none') - for rt in requesttype_list: - if not rt.deleted: - if rtid == rt.id: - select_reqtype.add_option(rt.name, rt.id, selected=True) - else: - select_reqtype.add_option(rt.name, rt.id) - return select_reqtype - def __show_request_form(self, trans, **kwd): - params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) + request_id = params.get( 'id', None ) try: - request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) ) + request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) ) except: - return trans.fill_template( '/requests/common/new_request.mako', - cntrller=cntrller, - select_request_type=self.__select_request_type(trans, 'none'), - widgets=[], - message=message, - status=status) - form_values = None - select_request_type = self.__select_request_type(trans, request_type.id) - # user - if cntrller == 'requests_admin' and trans.user_is_admin(): - user_id = params.get( 'select_user', 'none' ) - try: - user = trans.sa_session.query( trans.app.model.User ).get( int( user_id ) ) - except: - user = None - elif cntrller == 'requests': - user = trans.user - # list of widgets to be rendered on the request form + return invalid_id_redirect( trans, cntrller, request_id ) + name = util.restore_text( params.get( 'name', '' ) ) + desc = util.restore_text( params.get( 'desc', '' ) ) + if params.get( 'edit_basic_request_info_button', False ) or params.get( 'edit_samples_button', False ): + if not name: + status = 'error' + message = 'Enter the name of the request' + else: + request = self.__save_request( trans, cntrller, request=request, **kwd ) + message = 'The changes made to request (%s) have been saved.' % request.name + # Widgets to be rendered on the request form widgets = [] - if cntrller == 'requests_admin' and trans.user_is_admin(): - widgets.append(dict(label='Select user', - widget=self.__select_user(trans, user_id), - helptext='The request would be submitted on behalf of this user (Required)')) - widgets.append(dict(label='Name of the Experiment', - widget=TextField('name', 40, - util.restore_text( params.get( 'name', '' ) )), - helptext='(Required)')) - widgets.append(dict(label='Description', - widget=TextField('desc', 40, - util.restore_text( params.get( 'desc', '' ) )), - helptext='(Optional)')) - widgets = widgets + request_type.request_form.get_widgets( user, **kwd ) - return trans.fill_template( '/requests/common/new_request.mako', + widgets.append( dict( label='Name', + widget=TextField( 'name', 40, request.name ), + helptext='(Required)' ) ) + widgets.append( dict( label='Description', + widget=TextField( 'desc', 40, request.desc ), + helptext='(Optional)' ) ) + widgets = widgets + request.type.request_form.get_widgets( request.user, request.values.content, **kwd ) + # In case there is an error on the form, make sure to populate widget fields with anything the user + # may have already entered. + self.populate_widgets_from_kwd( trans, widgets, **kwd ) + return trans.fill_template( 'requests/common/edit_basic_request_info.mako', cntrller=cntrller, - select_request_type=select_request_type, - request_type=request_type, + request_type=request.type, + request=request, widgets=widgets, message=message, - status=status) - def __select_user(self, trans, userid): - user_list = trans.sa_session.query( trans.app.model.User )\ - .order_by( trans.app.model.User.email.asc() ) - user_ids = ['none'] - for user in user_list: - if not user.deleted: - user_ids.append(str(user.id)) - # gvk - 9/22/10: TODO: why does select_user require a refresh_on_change? Nothing in the - # code is apparent as to why this is done. - select_user = SelectField('select_user', - refresh_on_change=True, - refresh_on_change_values=user_ids[1:]) - if userid == 'none': - select_user.add_option('Select one', 'none', selected=True) + status=status ) + def __save_request( self, trans, cntrller, request=None, **kwd ): + """ + Saves changes to an existing request, or creates a new + request if received request is None. + """ + params = util.Params( kwd ) + request_type_id = params.get( 'request_type_id', None ) + is_admin = cntrller == 'requests_admin' and trans.user_is_admin() + if request is None: + # We're creating a new request, so we need the associated request_type + request_type = trans.sa_session.query( trans.model.RequestType ).get( trans.security.decode_id( request_type_id ) ) + if is_admin: + # The admin user is creating a request on behalf of another user + user_id = params.get( 'user_id', '' ) + user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) ) + else: + user = trans.user else: - select_user.add_option('Select one', 'none') - for user in user_list: - if not user.deleted: - if userid == str(user.id): - select_user.add_option(user.email, user.id, selected=True) - else: - select_user.add_option(user.email, user.id) - return select_user - def __save_request(self, trans, request, **kwd): - ''' - This method saves a new request if request_id is None. - ''' - params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - if request: + # We're saving changes to an existing request user = request.user request_type = request.type - else: - request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) ) - if cntrller == 'requests_admin' and trans.user_is_admin(): - user = trans.sa_session.query( trans.app.model.User ).get( int( params.get( 'select_user', '' ) ) ) - elif cntrller == 'requests': - user = trans.user - name = util.restore_text(params.get('name', '')) - desc = util.restore_text(params.get('desc', '')) - notification = dict(email=[user.email], sample_states=[request_type.last_state().id], body='', subject='') - # fields + name = util.restore_text( params.get( 'name', '' ) ) + desc = util.restore_text( params.get( 'desc', '' ) ) + notification = dict( email=[ user.email ], sample_states=[ request_type.state.id ], body='', subject='' ) values = [] - for index, field in enumerate(request_type.request_form.fields): - if field['type'] == 'AddressField': - value = util.restore_text(params.get('field_%i' % index, '')) + for index, field in enumerate( request_type.request_form.fields ): + field_type = field[ 'type' ] + field_value = params.get( 'field_%i' % index, '' ) + if field[ 'type' ] == 'AddressField': + value = util.restore_text( field_value ) if value == 'new': - # save this new address in the list of this user's addresses - user_address = trans.app.model.UserAddress( user=user ) + # Save this new address in the list of this user's addresses + user_address = trans.model.UserAddress( user=user ) self.save_widget_field( trans, user_address, index, **kwd ) trans.sa_session.refresh( user ) - values.append(int(user_address.id)) - elif value == unicode('none'): - values.append('') + values.append( int( user_address.id ) ) + elif value in [ '', 'none', 'None', None ]: + values.append( '' ) else: - values.append(int(value)) - elif field['type'] == 'CheckboxField': - values.append(CheckboxField.is_checked( params.get('field_%i' % index, '') )) + values.append( int( value ) ) + elif field[ 'type' ] == 'CheckboxField': + values.append( CheckboxField.is_checked( field_value )) else: - values.append(util.restore_text(params.get('field_%i' % index, ''))) - form_values = trans.app.model.FormValues(request_type.request_form, values) + values.append( util.restore_text( field_value ) ) + form_values = trans.model.FormValues( request_type.request_form, values ) trans.sa_session.add( form_values ) trans.sa_session.flush() - if not request: - request = trans.app.model.Request(name, desc, request_type, - user, form_values, notification) + if request is None: + # We're creating a new request + request = trans.model.Request( name, desc, request_type, user, form_values, notification ) trans.sa_session.add( request ) trans.sa_session.flush() trans.sa_session.refresh( request ) - # create an event with state 'New' for this new request - if request.user.email is not trans.user: - comments = "Request created by admin (%s) on behalf of %s." % (trans.user.email, request.user.email) + # Create an event with state 'New' for this new request + if request.user != trans.user: + sample_event_comment = "Request created by user %s for user %s." % ( trans.user.email, request.user.email ) else: - comments = "Request created." - event = trans.app.model.RequestEvent(request, request.states.NEW, comments) + sample_event_comment = "Request created." + event = trans.model.RequestEvent( request, request.states.NEW, sample_event_comment ) trans.sa_session.add( event ) trans.sa_session.flush() else: + # We're saving changes to an existing request request.name = name request.desc = desc request.type = request_type @@ -239,1068 +307,1034 @@ class RequestsCommon( BaseController, Us trans.sa_session.flush() return request @web.expose - @web.require_login( "create/submit sequencing requests" ) - def email_settings(self, trans, **kwd): + @web.require_login( "submit sequencing requests" ) + def submit_request( self, trans, cntrller, **kwd ): params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) + request_id = params.get( 'id', None ) + message = util.restore_text( params.get( 'message', '' ) ) + status = util.restore_text( params.get( 'status', 'done' ) ) try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id( params.get( 'id', None ) ) ) + request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) ) except: - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', + return invalid_id_redirect( trans, cntrller, request_id ) + ok = True + if not request.samples: + message = 'Add at least 1 sample to this request before submitting.' + ok = False + if ok: + message = self.__validate_request( trans, cntrller, request ) + if message or not ok: + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='edit_basic_request_info', + cntrller=cntrller, + id = request_id, status='error', - message="Invalid request ID") ) - email_user = CheckboxField.is_checked( params.get('email_user', '') ) - email_additional = params.get('email_additional', '').split('\r\n') - if email_user or email_additional: - emails = [] - if email_user: - emails.append(request.user.email) - for e in email_additional: - emails.append(util.restore_text(e)) - # check if valid email addresses - invalid = '' - for e in emails: - if len( e ) == 0 or "@" not in e or "." not in e: - invalid = e - break - if invalid: - message = "<b>%s</b> is not a valid email address." % invalid - return trans.response.send_redirect( web.url_for( controller='requests_common', - cntrller=cntrller, - action='edit', - show=True, - id=trans.security.encode_id(request.id), - message=message , - status='error' ) ) - else: - email_states = [] - for i, ss in enumerate(request.type.states): - if CheckboxField.is_checked( params.get('sample_state_%i' % ss.id, '') ): - email_states.append(ss.id) - request.notification = dict(email=emails, sample_states=email_states, - body='', subject='') - trans.sa_session.flush() - trans.sa_session.refresh( request ) - message = 'The changes made to the sequencing request has been saved' - return trans.response.send_redirect( web.url_for( controller='requests_common', - cntrller=cntrller, - action='show', - id=trans.security.encode_id(request.id), - message=message , - status='done') ) - @web.expose - @web.require_login( "create/submit sequencing requests" ) - def edit(self, trans, **kwd): - params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id( params.get( 'id', None ) ) ) - except: - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - status='error', - message="Invalid request ID") ) - if params.get('show', False) == 'True': - return self.__edit_request(trans, **kwd) - elif params.get('save_changes_request_button', False) == 'Save' \ - or params.get('edit_samples_button', False) == 'Edit samples': - if not util.restore_text(params.get('name', '')): - message = 'Please enter the <b>Name</b> of the request' - kwd['status'] = 'error' - kwd['message'] = message - kwd['show'] = 'True' - return trans.response.send_redirect( web.url_for( controller='requests_common', - cntrller=cntrller, - action='edit', - **kwd) ) - request = self.__save_request(trans, request, **kwd) - message = 'The changes made to the request named %s has been saved' % request.name - if params.get('save_changes_request_button', False) == 'Save': - return trans.response.send_redirect( web.url_for( controller=cntrller, - cntrller=cntrller, - action='list', - message=message , - status='done') ) - elif params.get('edit_samples_button', False) == 'Edit samples': - new_kwd = {} - new_kwd['id'] = request.id - new_kwd['edit_samples_button'] = 'Edit samples' - return trans.response.send_redirect( web.url_for( controller=cntrller, - cntrller=cntrller, - action='show', - message=message , - status='done', - **new_kwd) ) - elif params.get('refresh', False) == 'true': - return self.__edit_request(trans, **kwd) - def __edit_request(self, trans, **kwd): - try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) - except: - message = "Invalid request ID" - log.warn( message ) - return trans.response.send_redirect( web.url_for( controller=cntrller, - cntrller=cntrller, - action='list', - status='error', - message=message) ) - params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - #select_request_type = self.__select_request_type(trans, request.type.id) - # list of widgets to be rendered on the request form - widgets = [] - if util.restore_text( params.get( 'name', '' ) ): - name = util.restore_text( params.get( 'name', '' ) ) + message=message ) ) + # Change the request state to 'Submitted' + if request.user.email is not trans.user: + sample_event_comment = "Request submitted by %s on behalf of %s." % ( trans.user.email, request.user.email ) else: - name = request.name - widgets.append(dict(label='Name', - widget=TextField('name', 40, name), - helptext='(Required)')) - if util.restore_text( params.get( 'desc', '' ) ): - desc = util.restore_text( params.get( 'desc', '' ) ) - else: - desc = request.desc - widgets.append(dict(label='Description', - widget=TextField('desc', 40, desc), - helptext='(Optional)')) - widgets = widgets + request.type.request_form.get_widgets( request.user, request.values.content, **kwd ) - return trans.fill_template( 'requests/common/edit_request.mako', - cntrller=cntrller, - #select_request_type=select_request_type, - request_type=request.type, - request=request, - widgets=widgets, - message=message, - status=status) - def __validate(self, trans, cntrller, request): - ''' - Validates the request entered by the user - ''' - if not request.samples: - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='show', - message='Please add one or more samples to this request before submitting.', - status='error', - id=trans.security.encode_id(request.id)) ) - empty_fields = [] - # check rest of the fields of the form - for index, field in enumerate(request.type.request_form.fields): - if field['required'] == 'required' and request.values.content[index] in ['', None]: - empty_fields.append(field['label']) - if empty_fields: - message = 'Fill the following fields of the request <b>%s</b> before submitting<br/>' % request.name - for ef in empty_fields: - message = message + '<b>' +ef + '</b><br/>' - return message - return None - @web.expose - @web.require_login( "create/submit sequencing requests" ) - def submit(self, trans, **kwd): - params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) - except: - message = "Invalid request ID" - log.warn( message ) - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - status='error', - message=message, - **kwd) ) - message = self.__validate(trans, cntrller, request) - if message: - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='edit', - status = 'error', - message=message, - id=trans.security.encode_id(request.id) ) ) - # change the request state to 'Submitted' - if request.user.email is not trans.user: - comments = "Request submitted by admin (%s) on behalf of %s." % (trans.user.email, request.user.email) - else: - comments = "" - event = trans.app.model.RequestEvent(request, request.states.SUBMITTED, comments) + sample_event_comment = "" + event = trans.model.RequestEvent( request, request.states.SUBMITTED, sample_event_comment ) trans.sa_session.add( event ) - trans.sa_session.flush() # change the state of each of the samples of thus request new_state = request.type.states[0] - for s in request.samples: - event = trans.app.model.SampleEvent(s, new_state, 'Samples created.') + for sample in request.samples: + event = trans.model.SampleEvent( sample, new_state, 'Samples created.' ) trans.sa_session.add( event ) trans.sa_session.add( request ) trans.sa_session.flush() - request.send_email_notification(trans, new_state) + request.send_email_notification( trans, new_state ) + message = 'The request has been submitted.' return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - id=trans.security.encode_id(request.id), - status='done', - message='The request <b>%s</b> has been submitted.' % request.name - ) ) + action='browse_requests', + cntrller=cntrller, + id=request_id, + status=status, + message=message ) ) @web.expose - @web.require_login( "create/submit sequencing requests" ) - def delete(self, trans, **kwd): + @web.require_login( "sequencing request page" ) + def manage_request( self, trans, cntrller, **kwd ): params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - id_list = util.listify( kwd['id'] ) + is_admin = cntrller == 'requests_admin' and trans.user_is_admin() + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + request_id = params.get( 'id', None ) + try: + request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) ) + except: + return invalid_id_redirect( trans, cntrller, request_id ) + sample_state_id = params.get( 'sample_state_id', None ) + # Get the user entered sample information + current_samples, managing_samples, libraries = self.__get_sample_info( trans, request, **kwd ) + selected_samples = self.__get_selected_samples( trans, request, **kwd ) + selected_value = params.get( 'sample_operation', 'none' ) + if selected_value != 'none' and not selected_samples: + message = 'Select at least one sample before selecting an operation.' + status = 'error' + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', + cntrller=cntrller, + id=request_id, + status=status, + message=message ) ) + sample_operation_select_field = self.__build_sample_operation_select_field( trans, is_admin, request, selected_value ) + sample_operation_selected_value = sample_operation_select_field.get_selected( return_value=True ) + if params.get( 'import_samples_button', False ): + # Import sample field values from a csv file + return self.__import_samples( trans, cntrller, request, current_samples, libraries, **kwd ) + elif params.get( 'add_sample_button', False ): + return self.__add_sample( trans, cntrller, request, **kwd ) + elif params.get( 'save_samples_button', False ): + return self.__save_sample( trans, cntrller, request, current_samples, **kwd ) + elif params.get( 'edit_samples_button', False ): + managing_samples = True + elif params.get( 'cancel_changes_button', False ): + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', + cntrller=cntrller, + id=request_id ) ) + pass + elif params.get( 'change_state_button', False ): + sample_event_comment = util.restore_text( params.get( 'sample_event_comment', '' ) ) + new_state = trans.sa_session.query( trans.model.SampleState ).get( trans.security.decode_id( sample_state_id ) ) + for sample_id in selected_samples: + sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) ) + event = trans.model.SampleEvent( sample, new_state, sample_event_comment ) + trans.sa_session.add( event ) + trans.sa_session.flush() + return trans.response.send_redirect( web.url_for( controller='requests_common', + cntrller=cntrller, + action='update_request_state', + request_id=request_id ) ) + elif params.get( 'cancel_change_state_button', False ): + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', + cntrller=cntrller, + id=request_id ) ) + elif params.get( 'change_lib_button', False ): + library_id = params.get( 'sample_0_library_id', None ) + try: + library = trans.sa_session.query( trans.model.Library ).get( trans.security.decode_id( library_id ) ) + except: + invalid_id_redirect( trans, cntrller, library_id ) + folder_id = params.get( 'sample_0_folder_id', None ) + try: + folder = trans.sa_session.query( trans.model.LibraryFolder ).get( trans.security.decode_id( folder_id ) ) + except: + invalid_id_redirect( trans, cntrller, folder_id ) + for sample_id in selected_samples: + sample = trans.sa_session.query( trans.model.Sample ).get( sample_id ) + sample.library = library + sample.folder = folder + trans.sa_session.add( sample ) + trans.sa_session.flush() + trans.sa_session.refresh( request ) + message = 'Changes made to the selected samples have been saved. ' + elif params.get( 'cancel_change_lib_button', False ): + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', + cntrller=cntrller, + id=trans.security.encode_id( request.id ) ) ) + request_widgets = self.__get_request_widgets( trans, request.id ) + sample_copy = self.__build_copy_sample_select_field( trans, current_samples ) + libraries_select_field, folders_select_field = self.__build_library_and_folder_select_fields( trans, + request.user, + 0, + libraries, + None, + **kwd ) + # Build the sample_state_id_select_field SelectField + sample_state_id_select_field = self.__build_sample_state_id_select_field( trans, request, sample_state_id ) + return trans.fill_template( '/requests/common/manage_request.mako', + cntrller=cntrller, + request=request, + selected_samples=selected_samples, + request_widgets=request_widgets, + current_samples=current_samples, + sample_copy=sample_copy, + libraries=libraries, + sample_operation_select_field=sample_operation_select_field, + libraries_select_field=libraries_select_field, + folders_select_field=folders_select_field, + sample_state_id_select_field=sample_state_id_select_field, + managing_samples=managing_samples, + status=status, + message=message ) + @web.expose + @web.require_login( "delete sequencing requests" ) + def delete_request( self, trans, cntrller, **kwd ): + params = util.Params( kwd ) + id_list = util.listify( kwd.get( 'id', '' ) ) + message = util.restore_text( params.get( 'message', '' ) ) + status = util.restore_text( params.get( 'status', 'done' ) ) + num_deleted = 0 for id in id_list: + ok_for_now = True try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(id) ) + request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( id ) ) except: - message = "Invalid request ID" - log.warn( message ) - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - status='error', - message=message, - **kwd) ) - request.deleted = True - trans.sa_session.add( request ) - # delete all the samples belonging to this request - for s in request.samples: - s.deleted = True - trans.sa_session.add( s ) - trans.sa_session.flush() - message = '%i request(s) has been deleted.' % len(id_list) - status = 'done' + message += "Invalid request ID (%s). " % str( id ) + status = 'error' + ok_for_now = False + if ok_for_now: + request.deleted = True + trans.sa_session.add( request ) + # delete all the samples belonging to this request + for s in request.samples: + s.deleted = True + trans.sa_session.add( s ) + trans.sa_session.flush() + num_deleted += 1 + message += '%i requests have been deleted.' % num_deleted return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', + action='browse_requests', status=status, - message=message) ) + message=message ) ) @web.expose - @web.require_login( "create/submit sequencing requests" ) - def undelete(self, trans, **kwd): + @web.require_login( "undelete sequencing requests" ) + def undelete_request( self, trans, cntrller, **kwd ): params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - id_list = util.listify( kwd['id'] ) + id_list = util.listify( kwd.get( 'id', '' ) ) + message = util.restore_text( params.get( 'message', '' ) ) + status = util.restore_text( params.get( 'status', 'done' ) ) + num_undeleted = 0 for id in id_list: + ok_for_now = True try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(id) ) + request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( id ) ) except: - message = "Invalid request ID" - log.warn( message ) - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - status='error', - message=message, - **kwd) ) - request.deleted = False - trans.sa_session.add( request ) - # undelete all the samples belonging to this request - for s in request.samples: - s.deleted = False - trans.sa_session.add( s ) - trans.sa_session.flush() + message += "Invalid request ID (%s). " % str( id ) + status = 'error' + ok_for_now = False + if ok_for_now: + request.deleted = False + trans.sa_session.add( request ) + # undelete all the samples belonging to this request + for s in request.samples: + s.deleted = False + trans.sa_session.add( s ) + trans.sa_session.flush() + num_undeleted += 1 + message += '%i requests have been undeleted.' % num_deleted return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - status='done', - message='%i request(s) has been undeleted.' % len(id_list) ) ) + action='browse_requests', + status=status, + message=message ) ) @web.expose - @web.require_login( "create/submit sequencing requests" ) - def events(self, trans, **kwd): + @web.require_login( "sequencing request events" ) + def request_events( self, trans, cntrller, **kwd ): params = util.Params( kwd ) - cntrller = params.get( 'cntrller', 'requests' ) + request_id = params.get( 'id', None ) try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) + request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) ) except: - message = "Invalid request ID" - log.warn( message ) - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - status='error', - message=message) ) + return invalid_id_redirect( trans, cntrller, request_id ) events_list = [] - all_events = request.events - for event in all_events: - events_list.append((event.state, time_ago(event.update_time), event.comment)) + for event in request.events: + events_list.append( ( event.state, time_ago( event.update_time ), event.comment ) ) return trans.fill_template( '/requests/common/events.mako', cntrller=cntrller, - events_list=events_list, request=request) + events_list=events_list, + request=request ) @web.expose - @web.require_admin - def update_request_state( self, trans, **kwd ): + @web.require_login( "edit email notification settings" ) + def edit_email_settings( self, trans, cntrller, **kwd ): + """ + Allow for changing the email notification settings where email is sent to a list of users + whenever the request state changes to one selected for notification. + """ params = util.Params( kwd ) - cntrller = params.get( 'cntrller', 'requests' ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + request_id = params.get( 'id', None ) try: - request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', None ) ) ) + request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) ) except: - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message="Invalid request ID", - **kwd) ) - # check if all the samples of the current request are in the sample state - common_state = request.common_state() + return invalid_id_redirect( trans, cntrller, request_id ) + email_address = CheckboxField.is_checked( params.get( 'email_address', '' ) ) + additional_email_addresses = params.get( 'additional_email_addresses', '' ) + # Get the list of checked sample state CheckBoxFields + checked_sample_states = [] + for index, sample_state in enumerate( request.type.states ): + if CheckboxField.is_checked( params.get( 'sample_state_%i' % sample_state.id, '' ) ): + checked_sample_states.append( sample_state.id ) + if additional_email_addresses: + additional_email_addresses = additional_email_addresses.split( '\r\n' ) + if email_address or additional_email_addresses: + # The user added 1 or more email addresses + email_addresses = [] + if email_address: + email_addresses.append( request.user.email ) + for email_address in additional_email_addresses: + email_addresses.append( util.restore_text( email_address ) ) + # Make sure email addresses are valid + err_msg = '' + for email_address in additional_email_addresses: + err_msg += self.__validate_email( email_address ) + if err_msg: + status = 'error' + message += err_msg + else: + request.notification = dict( email=email_addresses, + sample_states=checked_sample_states, + body='', + subject='' ) + else: + # The user may have eliminated email addresses that were previously set + request.notification = None + if checked_sample_states: + message = 'All sample states have been unchecked since no email addresses have been selected or entered. ' + trans.sa_session.add( request ) + trans.sa_session.flush() + trans.sa_session.refresh( request ) + message += 'The changes made to the email notification settings have been saved.' + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='edit_basic_request_info', + cntrller=cntrller, + id=request_id, + message=message , + status=status ) ) + @web.expose + @web.require_login( "update sequencing request state" ) + def update_request_state( self, trans, cntrller, **kwd ): + params = util.Params( kwd ) + message = params.get( 'message', '' ) + status = params.get( 'status', 'done' ) + request_id = params.get( 'request_id', None ) + try: + request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) ) + except: + return invalid_id_redirect( trans, cntrller, request_id ) + # Make sure all the samples of the current request have the same state + common_state = request.samples_have_common_state if not common_state: - # if the current request state is complete and one of its samples moved from + # If the current request state is complete and one of its samples moved from # the final sample state, then move the request state to In-progress - if request.complete(): - status='done' - message = "One or more samples' state moved from the final sample state. Now request in '%s' state" % request.states.SUBMITTED - event = trans.app.model.RequestEvent(request, request.states.SUBMITTED, message) + if request.is_complete: + message = "At least 1 sample state moved from the final sample state, so now the request is in the '%s' state" % request.states.SUBMITTED + event = trans.model.RequestEvent( request, request.states.SUBMITTED, message ) trans.sa_session.add( event ) trans.sa_session.flush() - else: - message = '' - status = 'ok' - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='show', - id=trans.security.encode_id(request.id), + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', + cntrller=cntrller, + id=request_id, status=status, message=message ) ) final_state = False - if common_state.id == request.type.last_state().id: + request_type_state = request.type.state + if common_state.id == request_type_state.id: # since all the samples are in the final state, change the request state to 'Complete' - comments = "All samples of this request are in the last sample state (%s). " % request.type.last_state().name + comments = "All samples of this request are in the last sample state (%s). " % request_type_state.name state = request.states.COMPLETE final_state = True else: comments = "All samples are in %s state. " % common_state.name state = request.states.SUBMITTED - event = trans.app.model.RequestEvent(request, state, comments) + event = trans.model.RequestEvent(request, state, comments) trans.sa_session.add( event ) trans.sa_session.flush() # check if an email notification is configured to be sent when the samples # are in this state - retval = request.send_email_notification(trans, common_state, final_state) + retval = request.send_email_notification( trans, common_state, final_state ) if retval: message = comments + retval else: message = comments - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='show', + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', + cntrller=cntrller, id=trans.security.encode_id(request.id), status='done', message=message ) ) - @web.expose - @web.require_login( "create/submit sequencing requests" ) - def show(self, trans, **kwd): + def __save_sample( self, trans, cntrller, request, current_samples, **kwd ): + # Save all the new/unsaved samples entered by the user params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) status = params.get( 'status', 'done' ) - add_sample = params.get('add_sample', False) + managing_samples = util.string_as_bool( params.get( 'managing_samples', False ) ) + is_admin = cntrller == 'requests_admin' and trans.user_is_admin() + selected_value = params.get( 'sample_operation', 'none' ) + # Check for duplicate sample names + message = '' + for index in range( len( current_samples ) - len( request.samples ) ): + sample_index = index + len( request.samples ) + current_sample = current_samples[ sample_index ] + sample_name = current_sample[ 'name' ] + if not sample_name.strip(): + message = 'Enter the name of sample number %i' % sample_index + break + count = 0 + for i in range( len( current_samples ) ): + if sample_name == current_samples[ i ][ 'name' ]: + count += 1 + if count > 1: + message = "This request has %i samples with the name (%s). Samples belonging to a request must have unique names." % ( count, sample_name ) + break + if message: + selected_samples = self.__get_selected_samples( trans, request, **kwd ) + request_widgets = self.__get_request_widgets( trans, request.id ) + sample_copy = self.__build_copy_sample_select_field( trans, current_samples ) + sample_operation_select_field = self.__build_sample_operation_select_field( trans, is_admin, request, selected_value ) + status = 'error' + return trans.fill_template( '/requests/common/manage_request.mako', + cntrller=cntrller, + request=request, + selected_samples=selected_samples, + request_widgets=request_widgets, + current_samples=current_samples, + sample_copy=sample_copy, + managing_samples=managing_samples, + sample_operation_select_field=sample_operation_select_field, + status=status, + message=message ) + if not managing_samples: + for index in range( len( current_samples ) - len( request.samples ) ): + sample_index = len( request.samples ) + current_sample = current_samples[ sample_index ] + form_values = trans.model.FormValues( request.type.sample_form, current_sample[ 'field_values' ] ) + trans.sa_session.add( form_values ) + trans.sa_session.flush() + s = trans.model.Sample( current_sample[ 'name' ], + '', + request, + form_values, + current_sample[ 'barcode' ], + current_sample[ 'library' ], + current_sample[ 'folder' ] ) + trans.sa_session.add( s ) + trans.sa_session.flush() + else: + message = 'Changes made to the samples are saved. ' + for sample_index in range( len( current_samples ) ): + sample = request.samples[ sample_index ] + current_sample = current_samples[ sample_index ] + sample.name = current_sample[ 'name' ] + sample.library = current_sample[ 'library' ] + sample.folder = current_sample[ 'folder' ] + if request.is_submitted: + bc_message = self.__validate_barcode( trans, sample, current_sample[ 'barcode' ] ) + if bc_message: + status = 'error' + message += bc_message + else: + if not sample.bar_code: + # If this is a 'new' (still in its first state) sample + # change the state to the next + if sample.state.id == request.type.states[0].id: + event = trans.model.SampleEvent( sample, + request.type.states[1], + 'Sample added to the system' ) + trans.sa_session.add( event ) + trans.sa_session.flush() + # Now check if all the samples' barcode has been entered. + # If yes then send notification email if configured + common_state = request.samples_have_common_state + if common_state: + if common_state.id == request.type.states[1].id: + event = trans.model.RequestEvent( request, + request.states.SUBMITTED, + "All samples are in %s state." % common_state.name ) + trans.sa_session.add( event ) + trans.sa_session.flush() + request.send_email_notification( trans, request.type.states[1] ) + sample.bar_code = current_samples[sample_index]['barcode'] + trans.sa_session.add( sample ) + trans.sa_session.flush() + form_values = trans.sa_session.query( trans.model.FormValues ).get( sample.values.id ) + form_values.content = current_sample[ 'field_values' ] + trans.sa_session.add( form_values ) + trans.sa_session.flush() + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', + cntrller=cntrller, + id=trans.security.encode_id( request.id ), + status=status, + message=message ) ) + @web.expose + @web.require_login( "find samples" ) + def find_samples( self, trans, cntrller, **kwd ): + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + is_admin = cntrller == 'requests_admin' and trans.user_is_admin() + samples_list = [] + results = '' + if params.get( 'find_samples_button', False ): + search_string = kwd.get( 'search_box', '' ) + search_type = params.get( 'search_type', '' ) + request_states = util.listify( params.get( 'request_states', '' ) ) + samples = [] + if search_type == 'barcode': + samples = trans.sa_session.query( trans.model.Sample ) \ + .filter( and_( trans.model.Sample.table.c.deleted==False, + func.lower( trans.model.Sample.table.c.bar_code ).like( "%" + search_string.lower() + "%" ) ) ) \ + .order_by( trans.model.Sample.table.c.create_time.desc() ) + elif search_type == 'sample name': + samples = trans.sa_session.query( trans.model.Sample ) \ + .filter( and_( trans.model.Sample.table.c.deleted==False, + func.lower( trans.model.Sample.table.c.name ).like( "%" + search_string.lower() + "%" ) ) ) \ + .order_by( trans.model.Sample.table.c.create_time.desc() ) + elif search_type == 'dataset': + samples = trans.sa_session.query( trans.model.Sample ) \ + .filter( and_( trans.model.Sample.table.c.deleted==False, + trans.model.SampleDataset.table.c.sample_id==trans.model.Sample.table.c.id, + func.lower( trans.model.SampleDataset.table.c.name ).like( "%" + search_string.lower() + "%" ) ) ) \ + .order_by( trans.model.Sample.table.c.create_time.desc() ) + if is_admin: + for s in samples: + if not s.request.deleted and s.request.state in request_states: + samples_list.append( s ) + else: + for s in samples: + if s.request.user.id == trans.user.id and s.request.state in request_states and not s.request.deleted: + samples_list.append( s ) + results = 'There are %i samples matching the search parameters.' % len( samples_list ) + # Build the request_states SelectField + selected_value = kwd.get( 'request_states', trans.model.Request.states.SUBMITTED ) + states = [ v for k, v in trans.model.Request.states.items() ] + request_states = build_select_field( trans, + states, + 'self', + 'request_states', + selected_value=selected_value, + refresh_on_change=False, + multiple=True, + display='checkboxes' ) + # Build the search_type SelectField + selected_value = kwd.get( 'search_type', 'sample name' ) + types = [ 'sample name', 'barcode', 'dataset' ] + search_type = build_select_field( trans, types, 'self', 'search_type', selected_value=selected_value, refresh_on_change=False ) + # Build the search_box TextField + search_box = TextField( 'search_box', 50, kwd.get('search_box', '' ) ) + return trans.fill_template( '/requests/common/find_samples.mako', + cntrller=cntrller, + request_states=request_states, + samples=samples_list, + search_type=search_type, + results=results, + search_box=search_box ) + @web.expose + @web.require_login( "sample events" ) + def sample_events( self, trans, cntrller, **kwd ): + params = util.Params( kwd ) + status = params.get( 'status', 'done' ) + message = util.restore_text( params.get( 'message', '' ) ) + sample_id = params.get( 'sample_id', None ) try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) + sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) ) except: - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - status='error', - message="Invalid request ID") ) - # get all data libraries accessible to this user + return invalid_id_redirect( trans, cntrller, sample_id ) + events_list = [] + for event in sample.events: + events_list.append( ( event.state.name, + event.state.desc, + time_ago( event.update_time ), + event.comment ) ) + return trans.fill_template( '/requests/common/sample_events.mako', + cntrller=cntrller, + events_list=events_list, + sample=sample ) + def __add_sample( self, trans, cntrller, request, **kwd ): + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + managing_samples = util.string_as_bool( params.get( 'managing_samples', False ) ) + is_admin = cntrller == 'requests_admin' and trans.user_is_admin() + # Get the widgets for rendering the request form + request_widgets = self.__get_request_widgets( trans, request.id ) + current_samples, managing_samples, libraries = self.__get_sample_info( trans, request, **kwd ) + if not current_samples: + # Form field names are zero-based. + sample_index = 0 + else: + sample_index = len( current_samples ) + if params.get( 'add_sample_button', False ): + num_samples_to_add = int( params.get( 'num_sample_to_copy', 1 ) ) + # See if the user has selected a sample to copy. + copy_sample_index = int( params.get( 'copy_sample_index', -1 ) ) + for index in range( num_samples_to_add ): + if copy_sample_index != -1: + # The user has selected a sample to copy. + library_id = current_samples[ copy_sample_index][ 'library_select_field' ].get_selected( return_value=True ) + folder_id = current_samples[ copy_sample_index ][ 'folder_select_field' ].get_selected( return_value=True ) + name = current_samples[ copy_sample_index ][ 'name' ] + '_%i' % ( index ) + library_id = 'none' + folder_id = 'none' + field_values = [ val for val in current_samples[ copy_sample_index ][ 'field_values' ] ] + else: + # The user has not selected a sample to copy (may just be adding a sample). + library_id = None + folder_id = None + name = 'Sample_%i' % sample_index + field_values = [ '' for field in request.type.sample_form.fields ] + # Build the library_select_field and folder_select_field for the new sample being added. + library_select_field, folder_select_field = self.__build_library_and_folder_select_fields( trans, + user=request.user, + sample_index=sample_index, + libraries=libraries, + sample=None, + library_id=library_id, + folder_id=folder_id, + **kwd ) + # Append the new sample to the current list of samples for the request + current_samples.append( dict( name=name, + barcode='', + library=None, + library_id=library_id, + folder=None, + folder_id=folder_id, + field_values=field_values, + library_select_field=library_select_field, + folder_select_field=folder_select_field ) ) + selected_samples = self.__get_selected_samples( trans, request, **kwd ) + selected_value = params.get( 'sample_operation', 'none' ) + sample_operation_select_field = self.__build_sample_operation_select_field( trans, is_admin, request, selected_value ) + sample_copy = self.__build_copy_sample_select_field( trans, current_samples ) + return trans.fill_template( '/requests/common/manage_request.mako', + cntrller=cntrller, + request=request, + selected_samples=selected_samples, + request_widgets=request_widgets, + current_samples=current_samples, + sample_operation_select_field=sample_operation_select_field, + sample_copy=sample_copy, + managing_samples=managing_samples, + message=message, + status=status ) + def __get_sample_info( self, trans, request, **kwd ): + """ + Retrieves all user entered sample information and returns a + list of all the samples and their field values. + """ + params = util.Params( kwd ) + managing_samples = util.string_as_bool( params.get( 'managing_samples', False ) ) + # Bet all data libraries accessible to this user libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] ) + # Build the list of widgets which will be used to render each sample row on the request page current_samples = [] - for i, s in enumerate(request.samples): - lib_widget, folder_widget = self.__library_widgets(trans, request.user, i, libraries, s, **kwd) - current_samples.append(dict(name=s.name, - barcode=s.bar_code, - library=s.library, - folder=s.folder, - field_values=s.values.content, - lib_widget=lib_widget, - folder_widget=folder_widget)) - if add_sample: - lib_widget, folder_widget = self.__library_widgets(trans, request.user, - len(current_samples)+1, - libraries, None, **kwd) - current_samples.append(dict(name='Sample_%i' % (len(current_samples)+1), - barcode='', - library=None, - folder=None, - field_values=['' for field in request.type.sample_form.fields], - lib_widget=lib_widget, - folder_widget=folder_widget)) - bulk_lib_ops = self.__library_widgets(trans, request.user, 0, libraries, None, **kwd) - return trans.fill_template( '/requests/common/show_request.mako', - cntrller=cntrller, - request=request, selected_samples=[], - request_details=self.request_details(trans, request.id), - current_samples=current_samples, - sample_ops=self.__sample_operation_selectbox(trans, request, **kwd), - sample_copy=self.__copy_sample(current_samples), - details='hide', edit_mode=util.restore_text( params.get( 'edit_mode', 'False' ) ), - message=message, status=status, bulk_lib_ops=bulk_lib_ops ) - - def __update_samples(self, trans, request, **kwd): - ''' - This method retrieves all the user entered sample information and - returns an list of all the samples and their field values - ''' - params = util.Params( kwd ) - details = params.get( 'details', 'hide' ) - edit_mode = params.get( 'edit_mode', 'False' ) - # get all data libraries accessible to this user - libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] ) - - current_samples = [] - for i, s in enumerate(request.samples): - lib_widget, folder_widget = self.__library_widgets(trans, request.user, i, libraries, s, **kwd) - current_samples.append(dict(name=s.name, - barcode=s.bar_code, - library=s.library, - folder=s.folder, - field_values=s.values.content, - lib_widget=lib_widget, - folder_widget=folder_widget)) - if edit_mode == 'False': - sample_index = len(request.samples) + for index, sample in enumerate( request.samples ): + library_select_field, folder_select_field = self.__build_library_and_folder_select_fields( trans, + request.user, + index, + libraries, + sample, + **kwd ) + current_samples.append( dict( name=sample.name, + barcode=sample.bar_code, + library=sample.library, + folder=sample.folder, + field_values=sample.values.content, + library_select_field=library_select_field, + folder_select_field=folder_select_field ) ) + if not managing_samples: + sample_index = len( request.samples ) else: sample_index = 0 while True: - lib_id = None - folder_id = None - if params.get( 'sample_%i_name' % sample_index, '' ): - # data library + library_id = params.get( 'sample_%i_library_id' % sample_index, None ) + folder_id = params.get( 'sample_%i_folder_id' % sample_index, None ) + if params.get( 'sample_%i_name' % sample_index, False ): + # Data library try: - library = trans.sa_session.query( trans.app.model.Library ).get( int( params.get( 'sample_%i_library_id' % sample_index, None ) ) ) - lib_id = library.id + library = trans.sa_session.query( trans.model.Library ).get( trans.security.decode_id( library_id ) ) + #library_id = library.id except: library = None - # folder - try: - folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( int( params.get( 'sample_%i_folder_id' % sample_index, None ) ) ) - folder_id = folder.id - except: - if library: - folder = library.root_folder - else: - folder = None + if library is not None: + # Folder + try: + folder = trans.sa_session.query( trans.model.LibraryFolder ).get( trans.security.decode_id( folder_id ) ) + #folder_id = folder.id + except: + if library: + folder = library.root_folder + else: + folder = None + else: + folder = None sample_info = dict( name=util.restore_text( params.get( 'sample_%i_name' % sample_index, '' ) ), barcode=util.restore_text( params.get( 'sample_%i_barcode' % sample_index, '' ) ), library=library, folder=folder) - sample_info['field_values'] = [] - for field_index in range(len(request.type.sample_form.fields)): - sample_info['field_values'].append(util.restore_text( params.get( 'sample_%i_field_%i' % (sample_index, field_index), '' ) )) - if edit_mode == 'False': - sample_info['lib_widget'], sample_info['folder_widget'] = self.__library_widgets(trans, request.user, - sample_index, libraries, - None, lib_id, folder_id, **kwd) - current_samples.append(sample_info) + sample_info[ 'field_values' ] = [] + for field_index in range( len( request.type.sample_form.fields ) ): + sample_info[ 'field_values' ].append( util.restore_text( params.get( 'sample_%i_field_%i' % ( sample_index, field_index ), '' ) ) ) + if not managing_samples: + sample_info[ 'library_select_field' ], sample_info[ 'folder_select_field' ] = self.__build_library_and_folder_select_fields( trans, + request.user, + sample_index, + libraries, + None, + library_id, + folder_id, + **kwd ) + current_samples.append( sample_info ) else: - sample_info['lib_widget'], sample_info['folder_widget'] = self.__library_widgets(trans, - request.user, - sample_index, - libraries, - request.samples[sample_index], - **kwd) - current_samples[sample_index] = sample_info - sample_index = sample_index + 1 + sample_info[ 'library_select_field' ], sample_info[ 'folder_select_field' ] = self.__build_library_and_folder_select_fields( trans, + request.user, + sample_index, + libraries, + request.samples[ sample_index ], + **kwd ) + current_samples[ sample_index ] = sample_info + sample_index += 1 else: break - return current_samples, details, edit_mode, libraries - def __library_widgets(self, trans, user, sample_index, libraries, sample=None, lib_id=None, folder_id=None, **kwd): - ''' - This method creates the data library & folder selectbox for creating & - editing samples. First we get a list of all the libraries accessible to - the current user and display it in a selectbox. If the user has selected an - existing library then display all the accessible sub folders of the selected - data library. - ''' + return current_samples, managing_samples, libraries + @web.expose + @web.require_login( "delete sample from sequencing request" ) + def delete_sample( self, trans, cntrller, **kwd ): params = util.Params( kwd ) - # data library selectbox - if not lib_id: - lib_id = params.get( "sample_%i_library_id" % sample_index, 'none' ) - selected_lib = None - if sample and lib_id == 'none': - if sample.library: - lib_id = str(sample.library.id) - selected_lib = sample.library - # create data library selectbox with refresh on change enabled - lib_id_list = ['new'] + [str(lib.id) for lib in libraries.keys()] - lib_widget = SelectField( "sample_%i_library_id" % sample_index, - refresh_on_change=True, - refresh_on_change_values=lib_id_list ) - # fill up the options in the Library selectbox - # first option 'none' is the value for "Select one" option - if lib_id == 'none': - lib_widget.add_option('Select one', 'none', selected=True) + status = params.get( 'status', 'done' ) + message = util.restore_text( params.get( 'message', '' ) ) + request_id = params.get( 'request_id', None ) + try: + request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) ) + except: + return invalid_id_redirect( trans, cntrller, request_id ) + current_samples, managing_samples, libraries = self.__get_sample_info( trans, request, **kwd ) + sample_index = int( params.get( 'sample_id', 0 ) ) + sample_name = current_samples[sample_index]['name'] + sample = request.has_sample( sample_name ) + if sample: + trans.sa_session.delete( sample.values ) + trans.sa_session.delete( sample ) + trans.sa_session.flush() + message = 'Sample (%s) has been deleted.' % sample_name + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', + cntrller=cntrller, + id=trans.security.encode_id( request.id ), + status=status, + message=message ) ) + @web.expose + @web.require_login( "view data transfer page" ) + def view_dataset_transfer( self, trans, cntrller, **kwd ): + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + is_admin = cntrller == 'requests_admin' and trans.user_is_admin() + sample_id = params.get( 'sample_id', None ) + try: + sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) ) + except: + return invalid_id_redirect( trans, cntrller, sample_id ) + # check if a library and folder has been set for this sample yet. + if not sample.library or not sample.folder: + status = 'error' + message = "Set a data library and folder for sequencing request (%s) to transfer datasets." % sample.name + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', + cntrller=cntrller, + id=trans.security.encode_id( sample.request.id ), + status=status, + message=message ) ) + if is_admin: + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='manage_datasets', + sample_id=sample_id ) ) + + folder_path = util.restore_text( params.get( 'folder_path', '' ) ) + if not folder_path: + if len( sample.datasets ): + folder_path = os.path.dirname( sample.datasets[-1].file_path[:-1] ) + else: + folder_path = util.restore_text( sample.request.type.datatx_info.get( 'data_dir', '' ) ) + if folder_path and folder_path[-1] != os.sep: + folder_path += os.sep + if not sample.request.type.datatx_info['host'] \ + or not sample.request.type.datatx_info['username'] \ + or not sample.request.type.datatx_info['password']: + status = 'error' + message = 'The sequencer login information is incomplete. Click on sequencer information to add login details.' + return trans.fill_template( '/requests/common/dataset_transfer.mako', + cntrller=cntrller, + sample=sample, + dataset_files=sample.datasets, + message=message, + status=status, + files=[], + folder_path=folder_path ) + def __import_samples( self, trans, cntrller, request, current_samples, libraries, **kwd ): + """ + Reads the samples csv file and imports all the samples. The format of the csv file is: + SampleName,DataLibrary,DataLibraryFolder,Field1,Field2.... + """ + params = util.Params( kwd ) + managing_samples = util.string_as_bool( params.get( 'managing_samples', False ) ) + file_obj = params.get( 'file_data', '' ) + try: + reader = csv.reader( file_obj.file ) + for row in reader: + library_id = None + folder_id = None + # FIXME: this is bad - what happens when multiple libraries have the same name?? + lib = trans.sa_session.query( trans.model.Library ) \ + .filter( and_( trans.model.Library.table.c.name==row[1], + trans.model.Library.table.c.deleted==False ) ) \ + .first() + if lib: + folder = trans.sa_session.query( trans.model.LibraryFolder ) \ + .filter( and_( trans.model.LibraryFolder.table.c.name==row[2], + trans.model.LibraryFolder.table.c.deleted==False ) ) \ + .first() + if folder: + library_id = lib.id + folder_id = folder.id + library_select_field, folder_select_field = self.__build_library_and_folder_select_fields( trans, + request.user, + len( current_samples ), + libraries, + None, + library_id, + folder_id, + **kwd ) + current_samples.append( dict( name=row[0], + barcode='', + library=None, + folder=None, + library_select_field=library_select_field, + folder_select_field=folder_select_field, + field_values=row[3:] ) ) + except Exception, e: + status = 'error' + message = 'Error thrown when importing samples file: %s' % str( e ) + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='manage_request', + cntrller=cntrller, + id=trans.security.encode_id( request.id ), + status=status, + message=message ) ) + request_widgets = self.__get_request_widgets( trans, request.id ) + sample_copy = self.__build_copy_sample_select_field( trans, current_samples ) + return trans.fill_template( '/requests/common/manage_request.mako', + cntrller=cntrller, + request=request, + request_widgets=request_widgets, + current_samples=current_samples, + sample_copy=sample_copy, + managing_samples=managing_samples ) + # ===== Methods for handling form definition widgets ===== + def __get_request_widgets( self, trans, id ): + """Get the widgets for the request""" + request = trans.sa_session.query( trans.model.Request ).get( id ) + # The request_widgets list is a list of dictionaries + request_widgets = [] + for index, field in enumerate( request.type.request_form.fields ): + if field[ 'required' ]: + required_label = 'Required' + else: + required_label = 'Optional' + if field[ 'type' ] == 'AddressField': + if request.values.content[ index ]: + request_widgets.append( dict( label=field[ 'label' ], + value=trans.sa_session.query( trans.model.UserAddress ).get( int( request.values.content[ index ] ) ).get_html(), + helptext=field[ 'helptext' ] + ' (' + required_label + ')' ) ) + else: + request_widgets.append( dict( label=field[ 'label' ], + value=None, + helptext=field[ 'helptext' ] + ' (' + required_label + ')' ) ) + else: + request_widgets.append( dict( label=field[ 'label' ], + value=request.values.content[ index ], + helptext=field[ 'helptext' ] + ' (' + required_label + ')' ) ) + return request_widgets + def __get_samples_widgets( self, trans, request, libraries, **kwd ): + """Get the widgets for all of the samples currently associated with the request""" + # The current_samples_widgets list is a list of dictionaries + current_samples_widgets = [] + for index, sample in enumerate( request.samples ): + # Build the library_select_field and folder_select_field for each existing sample + library_select_field, folder_select_field = self.__build_library_and_folder_select_fields( trans, + user=request.user, + sample_index=index, + libraries=libraries, + sample=sample, + **kwd ) + # Append the dictionary for the current sample to the current_samples_widgets list + current_samples_widgets.append( dict( name=sample.name, + barcode=sample.bar_code, + library=sample.library, + folder=sample.folder, + field_values=sample.values.content, + library_select_field=library_select_field, + folder_select_field=folder_select_field ) ) + return current_samples_widgets + # ===== Methods for building SelectFields used on various request forms ===== + def __build_copy_sample_select_field( self, trans, current_samples ): + copy_sample_index_select_field = SelectField( 'copy_sample_index' ) + copy_sample_index_select_field.add_option( 'None', -1, selected=True ) + for index, sample_dict in enumerate( current_samples ): + copy_sample_index_select_field.add_option( sample_dict[ 'name' ], index ) + return copy_sample_index_select_field + def __build_request_type_id_select_field( self, trans, selected_value='none' ): + accessible_request_types = trans.user.accessible_request_types( trans ) + return build_select_field( trans, accessible_request_types, 'name', 'request_type_id', selected_value=selected_value, refresh_on_change=True ) + def __build_user_id_select_field( self, trans, selected_value='none' ): + active_users = trans.sa_session.query( trans.model.User ) \ + .filter( trans.model.User.table.c.deleted == False ) \ + .order_by( trans.model.User.email.asc() ) + # A refresh_on_change is required so the user's set of addresses can be displayed. + return build_select_field( trans, active_users, 'email', 'user_id', selected_value=selected_value, refresh_on_change=True ) + def __build_sample_operation_select_field( self, trans, is_admin, request, selected_value ): + # The sample_operation SelectField is displayed only after the request has been submitted. + # It's label is "For selected samples" + if is_admin: + if request.is_complete: + bulk_operations = [ trans.model.Sample.bulk_operations.CHANGE_STATE ] + if request.is_rejected: + bulk_operations = [ trans.model.Sample.bulk_operations.SELECT_LIBRARY ] + else: + bulk_operations = [ s for i, s in trans.model.Sample.bulk_operations.items() ] else: - lib_widget.add_option('Select one', 'none') - # all the libraries available to the selected user - for lib, hidden_folder_ids in libraries.items(): - if str(lib.id) == str(lib_id): - lib_widget.add_option(lib.name, lib.id, selected=True) - selected_lib, selected_hidden_folder_ids = lib, hidden_folder_ids.split(',') + if request.is_complete: + bulk_operations = [] else: - lib_widget.add_option(lib.name, lib.id) - lib_widget.refresh_on_change_values.append(lib.id) - # create the folder selectbox - folder_widget = SelectField( "sample_%i_folder_id" % sample_index ) - # when editing a request, either the user has already selected a subfolder or not - if sample: - if sample.folder: - current_fid = sample.folder.id - else: - # when a folder not yet associated with the request then the - # the current folder is set to the root_folder of the - # parent data library if present. - if sample.library: - current_fid = sample.library.root_folder.id - else: - current_fid = params.get( "sample_%i_folder_id" % sample_index, 'none' ) - else: - if folder_id: - current_fid = folder_id - else: - current_fid = 'none' - # first option - if lib_id == 'none': - folder_widget.add_option('Select one', 'none', selected=True) - else: - folder_widget.add_option('Select one', 'none') - if selected_lib: - # get all show-able folders for the selected library - showable_folders = trans.app.security_agent.get_showable_folders( user, user.all_roles(), - selected_lib, + bulk_operations = [ trans.model.Sample.bulk_operations.SELECT_LIBRARY ] + return build_select_field( trans, bulk_operations, 'self', 'sample_operation', selected_value=selected_value, refresh_on_change=True ) + def __build_library_and_folder_select_fields( self, trans, user, sample_index, libraries, sample=None, library_id=None, folder_id=None, **kwd ): + # Create the library_id SelectField for a specific sample. The received libraries param is a list of all the libraries + # accessible to the current user, and we add them as options to the library_select_field. If the user has selected an + # existing library then display all the accessible folders of the selected library in the folder_select_field. + # + # The libraries dictionary looks like: { library : '1,2' }, library : '3' }. Its keys are the libraries that + # should be displayed for the current user and its values are strings of comma-separated folder ids that should + # NOT be displayed. + # + # TODO: all object ids received in the params must be encoded. + params = util.Params( kwd ) + library_select_field_name= "sample_%i_library_id" % sample_index + folder_select_field_name = "sample_%i_folder_id" % sample_index + if not library_id: + library_id = params.get( library_select_field_name, 'none' ) + selected_library = None + selected_hidden_folder_ids = [] + showable_folders = [] + if sample and sample.library and library_id == 'none': + library_id = str( sample.library.id ) + selected_library = sample.library + # If we have a selected library, get the list of it's folders that are not accessible to the current user + for library, hidden_folder_ids in libraries.items(): + encoded_id = trans.security.encode_id( library.id ) + if encoded_id == str( library_id ): + selected_library = library + selected_hidden_folder_ids = hidden_folder_ids.split( ',' ) + break + # sample_%i_library_id SelectField with refresh on change enabled + library_select_field = build_select_field( trans, + libraries.keys(), + 'name', + library_select_field_name, + initial_value='none', + selected_value=str( library_id ).lower(), + refresh_on_change=True ) + # Get all accessible folders for the selected library, if one is indeed selected + if selected_library: + showable_folders = trans.app.security_agent.get_showable_folders( user, + user.all_roles(), + selected_library, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ], selected_hidden_folder_ids ) - for f in showable_folders: - if str(f.id) == str(current_fid): - folder_widget.add_option(f.name, f.id, selected=True) + if sample: + # The user is editing the request, and may have previously selected a folder + if sample.folder: + selected_folder_id = sample.folder.id + else: + # If a library is selected but not a folder, use the library's root folder + if sample.library: + selected_folder_id = sample.library.root_folder.id else: - folder_widget.add_option(f.name, f.id) - return lib_widget, folder_widget - def __copy_sample(self, current_samples): - copy_list = SelectField('copy_sample') - copy_list.add_option('None', -1, selected=True) - for i, s in enumerate(current_samples): - copy_list.add_option(s['name'], i) - return copy_list - def __sample_operation_selectbox(self, trans, request, **kwd): - params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - if cntrller == 'requests_admin' and trans.user_is_admin(): - if request.complete(): - bulk_operations = [trans.app.model.Sample.bulk_operations.CHANGE_STATE] - if request.rejected(): - bulk_operations = [trans.app.model.Sample.bulk_operations.SELECT_LIBRARY] + # The user just selected a folder + selected_folder_id = params.get( folder_select_field_name, 'none' ) + elif folder_id: + # TODO: not sure when this would be passed + selected_folder_id = folder_id + else: + selected_folder_id = 'none' + # TODO: Change the name of the library root folder to "Library root" to clarify to the + # user that it is the root folder. We probably should just change this in the Library code, + # and update the data in the db. + folder_select_field = build_select_field( trans, + showable_folders, + 'name', + folder_select_field_name, + initial_value='none', + selected_value=selected_folder_id ) + return library_select_field, folder_select_field + def __build_sample_state_id_select_field( self, trans, request, selected_value ): + if selected_value == 'none': + if request.samples: + selected_value = trans.security.encode_id( request.samples[0].state.id ) else: - bulk_operations = [s for i, s in trans.app.model.Sample.bulk_operations.items()] - else: - if request.complete(): - bulk_operations = [] - else: - bulk_operations = [trans.app.model.Sample.bulk_operations.SELECT_LIBRARY] - op_list = SelectField('select_sample_operation', - refresh_on_change=True, - refresh_on_change_values=bulk_operations) - sel_op = kwd.get('select_sample_operation', 'none') - if sel_op == 'none': - op_list.add_option('Select operation', 'none', True) - else: - op_list.add_option('Select operation', 'none') - for s in bulk_operations: - if s == sel_op: - op_list.add_option(s, s, True) - else: - op_list.add_option(s, s) - return op_list - def __selected_samples(self, trans, request, **kwd): - params = util.Params( kwd ) - selected_samples = [] - for s in request.samples: - if CheckboxField.is_checked(params.get('select_sample_%i' % s.id, '')): - selected_samples.append(s.id) - return selected_samples - @web.expose - @web.require_login( "create/submit sequencing requests" ) - def request_page(self, trans, **kwd): - params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id( kwd['id']) ) - except: - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - status='error', - message="Invalid request ID") ) - # get the user entered sample details - current_samples, details, edit_mode, libraries = self.__update_samples( trans, request, **kwd ) - selected_samples = self.__selected_samples(trans, request, **kwd) - sample_ops = self.__sample_operation_selectbox(trans, request,**kwd) - if params.get( 'select_sample_operation', False ) and not selected_samples: - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='show', - id=trans.security.encode_id(request.id), - status='error', - message='Select at least one sample before selecting an operation.' )) - if params.get( 'import_samples_button', False ): - return self.__import_samples(trans, cntrller, request, current_samples, details, libraries, **kwd) - elif params.get('add_sample_button', False ): - # add an empty or filled sample - # if the user has selected a sample no. to copy then copy the contents - # of the src sample to the new sample else an empty sample - src_sample_index = int(params.get( 'copy_sample', -1 ) ) - # get the number of new copies of the src sample - num_sample_to_copy = int( params.get( 'num_sample_to_copy', 1 ) ) - if src_sample_index == -1: - for ns in range( num_sample_to_copy ): - # empty sample - lib_widget, folder_widget = self.__library_widgets(trans, request.user, - len(current_samples), - libraries, None, **kwd) - current_samples.append(dict(name='Sample_%i' % (len(current_samples)+1), - barcode='', - library=None, - folder=None, - field_values=['' for field in request.type.sample_form.fields], - lib_widget=lib_widget, - folder_widget=folder_widget)) - else: - src_library_id = current_samples[src_sample_index]['lib_widget'].get_selected( return_value=True ) - src_folder_id = current_samples[src_sample_index]['folder_widget'].get_selected( return_value=True ) - for ns in range(num_sample_to_copy): - lib_widget, folder_widget = self.__library_widgets(trans, request.user, - len(current_samples), - libraries, sample=None, - lib_id=src_library_id, - folder_id=src_folder_id, - **kwd) - current_samples.append(dict(name=current_samples[src_sample_index]['name']+'_%i' % (len(current_samples)+1), - barcode='', - library_id='none', - folder_id='none', - field_values=[val for val in current_samples[src_sample_index]['field_values']], - lib_widget=lib_widget, - folder_widget=folder_widget)) - return trans.fill_template( '/requests/common/show_request.mako', - cntrller=cntrller, - request=request, - request_details=self.request_details(trans, request.id), - current_samples=current_samples, - sample_copy=self.__copy_sample(current_samples), - details=details, - selected_samples=selected_samples, - sample_ops=sample_ops, - edit_mode=edit_mode) - elif params.get( 'save_samples_button', False ): - # check for duplicate sample names - message = '' - for index in range(len(current_samples)-len(request.samples)): - sample_index = index + len(request.samples) - sample_name = current_samples[sample_index]['name'] - if not sample_name.strip(): - message = 'Please enter the name of sample number %i' % sample_index + selected_value = trans.security.encode_id( request.type.states[0].id ) + return build_select_field( trans, + objs=request.type.states, + label_attr='name', + select_field_name='sample_state_id', + selected_value=selected_value, + refresh_on_change=False ) + # ===== Methods for validation forms and fields ===== + def __validate_request( self, trans, cntrller, request ): + """Validates the request entered by the user""" + # TODO: Add checks for required sample fields here. + empty_fields = [] + # Make sure required form fields are filled in. + for index, field in enumerate( request.type.request_form.fields ): + if field[ 'required' ] == 'required' and request.values.content[ index ] in [ '', None ]: + empty_fields.append( field[ 'label' ] ) + if empty_fields: + message = 'Complete the following fields of the request before submitting: ' + for ef in empty_fields: + message += '<b>' + ef + '</b> ' + return message + return None + def __validate_barcode( self, trans, sample, barcode ): + """ + Makes sure that the barcode about to be assigned to a sample is gobally unique. + That is, barcodes must be unique across requests in Galaxy sample tracking. + """ + message = '' + unique = True + for index in range( len( sample.request.samples ) ): + # Check for empty bar code + if not barcode.strip(): + message = 'Fill in the barcode for sample (%s).' % sample.name + break + # TODO: Add a unique constraint to sample.bar_code table column + # Make sure bar code is unique + for sample_has_bar_code in trans.sa_session.query( trans.model.Sample ) \ + .filter( trans.model.Sample.table.c.bar_code == barcode ): + if sample_has_bar_code and sample_has_bar_code.id != sample.id: + message = '''The bar code (%s) associated with the sample (%s) belongs to another sample. + Bar codes must be unique across all samples, so use a different bar code + for this sample.''' % ( barcode, sample.name ) + unique = False break - count = 0 - for i in range(len(current_samples)): - if sample_name == current_samples[i]['name']: - count = count + 1 - if count > 1: - message = "This request has <b>%i</b> samples with the name <b>%s</b>.\nSamples belonging to a request must have unique names." % (count, sample_name) - break - if message: - return trans.fill_template( '/requests/common/show_request.mako', - cntrller=cntrller, - request=request, selected_samples=selected_samples, - request_details=self.request_details(trans, request.id), - current_samples = current_samples, - sample_copy=self.__copy_sample(current_samples), - details=details, edit_mode=edit_mode, - sample_ops=sample_ops, - status='error', message=message) - # save all the new/unsaved samples entered by the user - if edit_mode == 'False': - for index in range(len(current_samples)-len(request.samples)): - sample_index = len(request.samples) - form_values = trans.app.model.FormValues(request.type.sample_form, - current_samples[sample_index]['field_values']) - trans.sa_session.add( form_values ) - trans.sa_session.flush() - s = trans.app.model.Sample(current_samples[sample_index]['name'], '', - request, form_values, - current_samples[sample_index]['barcode'], - current_samples[sample_index]['library'], - current_samples[sample_index]['folder']) - trans.sa_session.add( s ) - trans.sa_session.flush() - - else: - status = 'done' - message = 'Changes made to the sample(s) are saved. ' - for sample_index in range(len(current_samples)): - sample = request.samples[sample_index] - sample.name = current_samples[sample_index]['name'] - sample.library = current_samples[sample_index]['library'] - sample.folder = current_samples[sample_index]['folder'] - if request.submitted(): - bc_message = self.__validate_barcode(trans, sample, current_samples[sample_index]['barcode']) - if bc_message: - status = 'error' - message += bc_message - else: - if not sample.bar_code: - # if this is a 'new' (still in its first state) sample - # change the state to the next - if sample.current_state().id == request.type.states[0].id: - event = trans.app.model.SampleEvent(sample, - request.type.states[1], - 'Sample added to the system') - trans.sa_session.add( event ) - trans.sa_session.flush() - # now check if all the samples' barcode has been entered. - # If yes then send notification email if configured - common_state = request.common_state() - if common_state: - if common_state.id == request.type.states[1].id: - event = trans.app.model.RequestEvent(request, - request.states.SUBMITTED, - "All samples are in %s state." % common_state.name) - trans.sa_session.add( event ) - trans.sa_session.flush() - request.send_email_notification(trans, request.type.states[1]) - sample.bar_code = current_samples[sample_index]['barcode'] - trans.sa_session.add( sample ) - trans.sa_session.flush() - form_values = trans.sa_session.query( trans.app.model.FormValues ).get( sample.values.id ) - form_values.content = current_samples[sample_index]['field_values'] - trans.sa_session.add( form_values ) - trans.sa_session.flush() - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='show', - id=trans.security.encode_id(request.id), - status=status, - message=message )) - elif params.get( 'edit_samples_button', False ): - edit_mode = 'True' - return trans.fill_template( '/requests/common/show_request.mako', - cntrller=cntrller, - request=request, selected_samples=selected_samples, - request_details=self.request_details(trans, request.id), - current_samples=current_samples, - sample_copy=self.__copy_sample(current_samples), - sample_ops=sample_ops, - details=details, libraries=libraries, - edit_mode=edit_mode) - elif params.get( 'cancel_changes_button', False ): - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='show', - id=trans.security.encode_id(request.id)) ) - elif params.get( 'change_state_button', False ) == 'Save': - comments = util.restore_text( params.comment ) - selected_state = int( params.select_state ) - new_state = trans.sa_session.query( trans.app.model.SampleState ).get( selected_state ) - for sample_id in selected_samples: - sample = trans.sa_session.query( trans.app.model.Sample ).get( sample_id ) - event = trans.app.model.SampleEvent(sample, new_state, comments) - trans.sa_session.add( event ) - trans.sa_session.flush() - return trans.response.send_redirect( web.url_for( controller='requests_common', - cntrller=cntrller, - action='update_request_state', - request_id=request.id )) - elif params.get( 'change_state_button', False ) == 'Cancel': - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='show', - id=trans.security.encode_id(request.id)) ) - elif params.get( 'change_lib_button', False ) == 'Save': - library = trans.sa_session.query( trans.app.model.Library ).get( int( params.get( 'sample_0_library_id', None ) ) ) - folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( int( params.get( 'sample_0_folder_id', None ) ) ) - for sample_id in selected_samples: - sample = trans.sa_session.query( trans.app.model.Sample ).get( sample_id ) - sample.library = library - sample.folder = folder - trans.sa_session.add( sample ) - trans.sa_session.flush() - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='show', - id=trans.security.encode_id(request.id), - status='done', - message='Changes made to the selected sample(s) are saved. ') ) - elif params.get( 'change_lib_button', False ) == 'Cancel': - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='show', - id=trans.security.encode_id(request.id)) ) - else: - return trans.fill_template( '/requests/common/show_request.mako', - cntrller=cntrller, - request=request, selected_samples=selected_samples, - request_details=self.request_details(trans, request.id), - current_samples=current_samples, - sample_copy=self.__copy_sample(current_samples), - details=details, libraries=libraries, - sample_ops=sample_ops, - edit_mode=edit_mode, status=status, message=message, - bulk_lib_ops=self.__library_widgets(trans, request.user, 0, libraries, None, **kwd)) - def __import_samples(self, trans, cntrller, request, current_samples, details, libraries, **kwd): - ''' - This method reads the samples csv file and imports all the samples - The format of the csv file is: - SampleName,DataLibrary,DataLibraryFolder,Field1,Field2.... - ''' - try: - params = util.Params( kwd ) - edit_mode = params.get( 'edit_mode', 'False' ) - file_obj = params.get('file_data', '') - reader = csv.reader(file_obj.file) - for row in reader: - lib_id = None - folder_id = None - lib = trans.sa_session.query( trans.app.model.Library ) \ - .filter( and_( trans.app.model.Library.table.c.name==row[1], \ - trans.app.model.Library.table.c.deleted==False ) )\ - .first() - if lib: - folder = trans.sa_session.query( trans.app.model.LibraryFolder ) \ - .filter( and_( trans.app.model.LibraryFolder.table.c.name==row[2], \ - trans.app.model.LibraryFolder.table.c.deleted==False ) )\ - .first() - if folder: - lib_id = lib.id - folder_id = folder.id - lib_widget, folder_widget = self.__library_widgets(trans, request.user, len(current_samples), - libraries, None, lib_id, folder_id, **kwd) - current_samples.append(dict(name=row[0], - barcode='', - library=None, - folder=None, - lib_widget=lib_widget, - folder_widget=folder_widget, - field_values=row[3:])) - return trans.fill_template( '/requests/common/show_request.mako', - cntrller=cntrller, - request=request, - request_details=self.request_details(trans, request.id), - current_samples=current_samples, - sample_copy=self.__copy_sample(current_samples), - details=details, - edit_mode=edit_mode) - except: - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='show', - id=trans.security.encode_id(request.id), - status='error', - message='Error in importing samples file' )) - def __validate_barcode(self, trans, sample, barcode): - ''' - This method makes sure that the given barcode about to be assigned to - the given sample is gobally unique. That is, barcodes must be unique - across requests in Galaxy LIMS - ''' - message = '' - for index in range(len(sample.request.samples)): - # check for empty bar code - if not barcode.strip(): - message = 'Please fill the barcode for sample <b>%s</b>.' % sample.name - break - # check all the saved bar codes - all_samples = trans.sa_session.query( trans.app.model.Sample ) - for s in all_samples: - if barcode == s.bar_code: - if sample.id == s.id: - continue - else: - message = '''The bar code <b>%s</b> of sample <b>%s</b> - belongs another sample. The sample bar codes must be - unique throughout the system''' % \ - (barcode, sample.name) - break - if message: + if not unique: break return message - @web.expose - @web.require_login( "create/submit sequencing requests" ) - def delete_sample(self, trans, **kwd): - params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', 0 ) ) ) - current_samples, details, edit_mode, libraries = self.__update_samples( trans, request, **kwd ) - sample_index = int(params.get('sample_id', 0)) - sample_name = current_samples[sample_index]['name'] - s = request.has_sample(sample_name) - if s: - trans.sa_session.delete( s.values ) - trans.sa_session.delete( s ) - trans.sa_session.flush() - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='show', - id=trans.security.encode_id(request.id), - status='done', - message='Sample <b>%s</b> has been deleted.' % sample_name )) - def request_details(self, trans, id): - ''' - Shows the request details - ''' - request = trans.sa_session.query( trans.app.model.Request ).get( id ) - # list of widgets to be rendered on the request form - request_details = [] - # form fields - for index, field in enumerate(request.type.request_form.fields): - if field['required']: - req = 'Required' - else: - req = 'Optional' - if field['type'] == 'AddressField': - if request.values.content[index]: - request_details.append(dict(label=field['label'], - value=trans.sa_session.query( trans.app.model.UserAddress ).get( int( request.values.content[index] ) ).get_html(), - helptext=field['helptext']+' ('+req+')')) - else: - request_details.append(dict(label=field['label'], - value=None, - helptext=field['helptext']+' ('+req+')')) - else: - request_details.append(dict(label=field['label'], - value=request.values.content[index], - helptext=field['helptext']+' ('+req+')')) - return request_details - @web.expose - @web.require_login( "create/submit sequencing requests" ) - def sample_events(self, trans, **kwd): - params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - try: - sample_id = int(params.get('sample_id', False)) - sample = trans.sa_session.query( trans.app.model.Sample ).get( sample_id ) - except: - message = "Invalid sample ID" - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - status='error', - message=message) ) - events_list = [] - all_events = sample.events - for event in all_events: - events_list.append((event.state.name, event.state.desc, - time_ago(event.update_time), - event.comment)) - return trans.fill_template( '/requests/common/sample_events.mako', - cntrller=cntrller, - events_list=events_list, - sample=sample) - @web.expose - @web.require_admin - def show_datatx_page( self, trans, **kwd ): - params = util.Params( kwd ) - cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - try: - sample = trans.sa_session.query( trans.app.model.Sample ).get( trans.security.decode_id( kwd['sample_id'] ) ) - except: - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - status='error', - message="Invalid sample ID") ) - # check if a library and folder has been set for this sample yet. - if not sample.library or not sample.folder: - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='list', - operation='show', - status='error', - message="Set a data library and folder for <b>%s</b> to transfer dataset(s)." % sample.name, - id=trans.security.encode_id(sample.request.id) ) ) - if cntrller == 'requests_admin': - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='manage_datasets', - sample_id=sample.id) ) - - if params.get( 'folder_path', '' ): - folder_path = util.restore_text( params.get( 'folder_path', '' ) ) - else: - if len(sample.datasets): - folder_path = os.path.dirname(sample.datasets[-1].file_path[:-1]) - else: - folder_path = util.restore_text( sample.request.type.datatx_info.get('data_dir', '') ) - if folder_path and folder_path[-1] != os.sep: - folder_path += os.sep - if not sample.request.type.datatx_info['host'] or not sample.request.type.datatx_info['username'] \ - or not sample.request.type.datatx_info['password']: - status = 'error' - message = 'The sequencer login information is incomplete. Click on the <b>Sequencer information</b> to add login details.' - return trans.fill_template( '/requests/common/get_data.mako', - cntrller=cntrller, sample=sample, - dataset_files=sample.datasets, - message=message, status=status, files=[], - folder_path=folder_path ) - # Find sequencing requests & samples - def __find_widgets(self, trans, **kwd): - params = util.Params( kwd ) - request_states = SelectField('request_states', multiple=True, display="checkboxes") - sel_op = kwd.get('request_states', trans.app.model.Request.states.SUBMITTED) - for i, s in trans.app.model.Request.states.items(): - if s in sel_op: - request_states.add_option(s, s, True) - else: - request_states.add_option(s, s) - search_type = SelectField('search_type') - sel_op = kwd.get('search_type', 'sample name') - for s in ['sample name', 'barcode', 'dataset']: - if s in sel_op: - search_type.add_option(s, s, True) - else: - search_type.add_option(s, s) - search_box = TextField('search_box', 50, kwd.get('search_box', '')) - return request_states, search_type, search_box - @web.expose - @web.require_admin - def find( self, trans, **kwd ): - params = util.Params( kwd ) - cntrller = params.get( 'cntrller', 'requests' ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - samples_list = [] - results = '' - if params.get('go_button', '') == 'Find': - search_string = kwd.get( 'search_box', '' ) - search_type = params.get( 'search_type', '' ) - request_states = util.listify( params.get( 'request_states', '' ) ) - samples = [] - if search_type == 'barcode': - samples = trans.sa_session.query( trans.app.model.Sample ) \ - .filter( and_( trans.app.model.Sample.table.c.deleted==False, - trans.app.model.Sample.table.c.bar_code.like(search_string) ) )\ - .order_by( trans.app.model.Sample.table.c.create_time.desc())\ - .all() - elif search_type == 'sample name': - samples = trans.sa_session.query( trans.app.model.Sample ) \ - .filter( and_( trans.app.model.Sample.table.c.deleted==False, - trans.app.model.Sample.table.c.name.ilike(search_string) ) )\ - .order_by( trans.app.model.Sample.table.c.create_time.desc())\ - .all() - elif search_type == 'dataset': - samples = trans.sa_session.query( trans.app.model.Sample ) \ - .filter( and_( trans.app.model.Sample.table.c.deleted==False, - trans.app.model.SampleDataset.table.c.sample_id==trans.app.model.Sample.table.c.id, - trans.app.model.SampleDataset.table.c.name.ilike(search_string) ) )\ - .order_by( trans.app.model.Sample.table.c.create_time.desc())\ - .all() - if cntrller == 'requests': - for s in samples: - if s.request.user.id == trans.user.id \ - and s.request.state() in request_states\ - and not s.request.deleted: - samples_list.append(s) - elif cntrller == 'requests_admin': - for s in samples: - if not s.request.deleted \ - and s.request.state() in request_states: - samples_list.append(s) - results = 'There are %i sample(s) matching the search parameters.' % len(samples_list) - request_states, search_type, search_box = self.__find_widgets(trans, **kwd) - return trans.fill_template( '/requests/common/find.mako', - cntrller=cntrller, request_states=request_states, - samples=samples_list, search_type=search_type, - results=results, search_box=search_box ) + def __validate_email( self, email ): + error = '' + if len( email ) == 0 or "@" not in email or "." not in email: + error = "(%s) is not a valid email address. " % str( email ) + elif len( email ) > 255: + error = "(%s) exceeds maximum allowable length. " % str( email ) + return error + # ===== Other miscellaneoud utility methods ===== + def __get_selected_samples( self, trans, request, **kwd ): + selected_samples = [] + for sample in request.samples: + if CheckboxField.is_checked( kwd.get( 'select_sample_%i' % sample.id, '' ) ): + selected_samples.append( trans.security.encode_id( sample.id ) ) + return selected_samples + +# ===== Miscellaneoud utility methods outside of the RequestsCommon class ===== +def invalid_id_redirect( trans, cntrller, obj_id, action='browse_requests' ): + status = 'error' + message = "Invalid request id (%s)" % str( obj_id ) + return trans.response.send_redirect( web.url_for( controller=cntrller, + action=action, + status=status, + message=message ) ) --- /dev/null +++ b/templates/requests/common/dataset_transfer.mako @@ -0,0 +1,47 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + +%if message: + ${render_msg( message, status )} +%endif + +<h2>Datasets of Sample "${sample.name}"</h2> + +<ul class="manage-table-actions"> + <li> + <a class="action-button" href="${h.url_for( controller='requests_common', action='view_dataset_transfer', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">Refresh</a> + </li> + <li> + <a class="action-button" href="${h.url_for( controller='library_common', action='browse_library', cntrller=cntrller, id=trans.security.encode_id( sample.library.id ) )}">${sample.library.name} Data Library</a> + </li> + <li> + <a class="action-button" href="${h.url_for( controller='requests_common', action='manage_request', cntrller=cntrller, id=trans.security.encode_id( sample.request.id ) )}">Browse this request</a> + </li> +</ul> + +%if dataset_files: + <div class="form-row"> + <table class="grid"> + <thead> + <tr> + <th>Name</th> + <th>Size</th> + <th>Status</th> + </tr> + <thead> + <tbody> + %for dataset_file in dataset_files: + <tr> + <td>${dataset_file.name}</td> + <td>${dataset_file.size}</td> + <td>${dataset_file.status}</td> + </tr> + %endfor + </tbody> + </table> + </div> +%else: + <div class="form-row"> + There are no datasets associated with this sample. + </div> +%endif --- a/templates/admin/requests/create_request_type.mako +++ b/templates/admin/requests/create_request_type.mako @@ -1,7 +1,6 @@ <%inherit file="/base.mako"/><%namespace file="/message.mako" import="render_msg" /> - %if message: ${render_msg( message, status )} %endif @@ -88,6 +87,5 @@ <input type="submit" name="save_request_type" value="Save"/></div></form> - %endif </div> --- a/templates/requests/common/sample_state.mako +++ b/templates/requests/common/sample_state.mako @@ -1,5 +1,5 @@ <%def name="render_sample_state( cntrller, sample )"> - <a href="${h.url_for( controller='requests_common', cntrller=cntrller, action='sample_events', sample_id=sample.id)}">${sample.current_state().name}</a> + <a href="${h.url_for( controller='requests_common', action='sample_events', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${sample.state.name}</a></%def> ${render_sample_state( cntrller, sample )} --- a/templates/admin/requests/datasets_grid.mako +++ b/templates/admin/requests/datasets_grid.mako @@ -1,6 +1,3 @@ - - - <%def name="custom_javascripts()"><script type="text/javascript"> $("#select-dataset-action-button").bind( "click", function(e) { @@ -10,7 +7,7 @@ error: function() { alert( "Couldn't create new browser" ) }, success: function(form_html) { show_modal("Select file", form_html, { - "Cancel": function() { window.location = "${h.url_for( controller='requests_admin', action='list' )}"; }, + "Cancel": function() { window.location = "${h.url_for( controller='requests_admin', action='browse_requests' )}"; }, "Continue": function() { $(document).trigger("convert_dbkeys"); continue_fn(); } }); $("#new-title").focus(); @@ -28,4 +25,3 @@ </%def><%inherit file="/grid_base.mako"/> - --- a/templates/requests/common/get_data.mako +++ /dev/null @@ -1,61 +0,0 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> - - -%if message: - ${render_msg( message, status )} -%endif - - - -<h2>Datasets of Sample "${sample.name}"</h2> - -<ul class="manage-table-actions"> - <li> - <a class="action-button" href="${h.url_for( controller='requests_common', cntrller=cntrller, action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}"> - <span>Refresh</span></a> - </li> - <li> - <a class="action-button" href="${h.url_for( controller='library_common', action='browse_library', cntrller='library', id=trans.security.encode_id( sample.library.id ) )}"> - <span>${sample.library.name} Data Library</span></a> - </li> - <li> - <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='show', id=trans.security.encode_id(sample.request.id) )}"> - <span>Browse this request</span></a> - </li> -</ul> - - -%if len(dataset_files): - <div class="form-row"> - <table class="grid"> - <thead> - <tr> - <th>Name</th> - <th>Size</th> - <th>Status</th> - </tr> - <thead> - <tbody> - %for dataset_index, dataset_file in enumerate(dataset_files): - <tr> - - <td> - ${dataset_file.name} - </td> - <td> - ${dataset_file.size} - </td> - <td> - ${dataset_file.status} - </td> - </tr> - %endfor - </tbody> - </table> - </div> -%else: - <div class="form-row"> - There are no dataset files associated with this sample. - </div> -%endif --- a/templates/admin/requests/manage_request_types.mako +++ /dev/null @@ -1,1 +0,0 @@ -<%inherit file="/grid_base.mako"/>