diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py
index cd255125801..04695c3ac3a 100644
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -1260,11 +1260,12 @@ class FormValues( object ):
self.content = content
class Request( object ):
- states = Bunch( UNSUBMITTED = 'Unsubmitted',
+ states = Bunch( NEW = 'New',
SUBMITTED = 'Submitted',
+ REJECTED = 'Rejected',
COMPLETE = 'Complete')
def __init__(self, name=None, desc=None, request_type=None, user=None,
- form_values=None, library=None, folder=None, state=False):
+ form_values=None, library=None, folder=None):
self.name = name
self.desc = desc
self.type = request_type
@@ -1272,19 +1273,39 @@ class Request( object ):
self.user = user
self.library = library
self.folder = folder
- self.state = state
self.samples_list = []
+ def state(self):
+ if self.events:
+ return self.events[0].state
+ return None
+ def last_comment(self):
+ if self.events:
+ if self.events[0].comment:
+ return self.events[0].comment
+ else:
+ return ''
+ return 'No comment'
def has_sample(self, sample_name):
for s in self.samples:
if s.name == sample_name:
return s
return False
- def submitted(self):
- return self.state == self.states.SUBMITTED
def unsubmitted(self):
- return self.state == self.states.UNSUBMITTED
+ 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
+ return self.state() == self.states.COMPLETE
+
+class RequestEvent( object ):
+ def __init__(self, request=None, request_state=None, comment=''):
+ self.request = request
+ self.state = request_state
+ self.comment = comment
class RequestType( object ):
def __init__(self, name=None, desc=None, request_form=None, sample_form=None):
diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py
index b6f15afc29d..1d4f9c2a1fe 100644
--- a/lib/galaxy/model/mapping.py
+++ b/lib/galaxy/model/mapping.py
@@ -618,9 +618,16 @@ Request.table = Table('request', metadata,
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "library_id", Integer, ForeignKey( "library.id" ), index=True ),
Column( "folder_id", Integer, ForeignKey( "library_folder.id" ), index=True ),
- Column( "state", TrimmedString( 255 ), index=True ),
Column( "deleted", Boolean, index=True, default=False ) )
+RequestEvent.table = Table('request_event', metadata,
+ Column( "id", Integer, primary_key=True),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "request_id", Integer, ForeignKey( "request.id" ), index=True ),
+ Column( "state", TrimmedString( 255 ), index=True ),
+ Column( "comment", TEXT ) )
+
Sample.table = Table('sample', metadata,
Column( "id", Integer, primary_key=True ),
Column( "create_time", DateTime, default=now ),
@@ -768,9 +775,14 @@ assign_mapper( context, Request, Request.table,
folder=relation( LibraryFolder,
primaryjoin=( Request.table.c.folder_id == LibraryFolder.table.c.id ) ),
library=relation( Library,
- primaryjoin=( Request.table.c.library_id == Library.table.c.id ) )
+ primaryjoin=( Request.table.c.library_id == Library.table.c.id ) ),
+ events=relation( RequestEvent, backref="request",
+ order_by=desc(RequestEvent.table.c.update_time) )
) )
+assign_mapper( context, RequestEvent, RequestEvent.table,
+ properties=None )
+
assign_mapper( context, RequestType, RequestType.table,
properties=dict( states=relation( SampleState,
backref="request_type",
diff --git a/lib/galaxy/model/migrate/versions/0027_request_events.py b/lib/galaxy/model/migrate/versions/0027_request_events.py
new file mode 100644
index 00000000000..70df9ebed6b
--- /dev/null
+++ b/lib/galaxy/model/migrate/versions/0027_request_events.py
@@ -0,0 +1,94 @@
+"""
+This migration script adds the request_event table and
+removes the state field in the request table
+"""
+from sqlalchemy import *
+from sqlalchemy.orm import *
+from sqlalchemy.exc import *
+from migrate import *
+from migrate.changeset import *
+
+import datetime
+now = datetime.datetime.utcnow
+
+import sys, logging
+log = logging.getLogger( __name__ )
+log.setLevel(logging.DEBUG)
+handler = logging.StreamHandler( sys.stdout )
+format = "%(name)s %(levelname)s %(asctime)s %(message)s"
+formatter = logging.Formatter( format )
+handler.setFormatter( formatter )
+log.addHandler( handler )
+
+# Need our custom types, but don't import anything else from model
+from galaxy.model.custom_types import *
+
+metadata = MetaData( migrate_engine )
+db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) )
+
+def display_migration_details():
+ print "========================================"
+ print "This migration script adds the request_event table and"
+ print "removes the state field in the request table"
+ print "========================================"
+
+def localtimestamp():
+ if migrate_engine.name == 'postgres' or migrate_engine.name == 'mysql':
+ return "LOCALTIMESTAMP"
+ elif migrate_engine.name == 'sqlite':
+ return "current_date || ' ' || current_time"
+ else:
+ raise Exception( 'Unable to convert data for unknown database type: %s' % db )
+
+def nextval( table, col='id' ):
+ if migrate_engine.name == 'postgres':
+ return "nextval('%s_%s_seq')" % ( table, col )
+ elif migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite':
+ return "null"
+ else:
+ raise Exception( 'Unable to convert data for unknown database type: %s' % migrate_engine.name )
+
+
+RequestEvent_table = Table('request_event', metadata,
+ Column( "id", Integer, primary_key=True),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "request_id", Integer, ForeignKey( "request.id" ), index=True ),
+ Column( "state", TrimmedString( 255 ), index=True ),
+ Column( "comment", TEXT ) )
+
+def upgrade():
+ display_migration_details()
+ # Load existing tables
+ metadata.reflect()
+ # Add new request_event table
+ try:
+ RequestEvent_table.create()
+ except Exception, e:
+ log.debug( "Creating request_event table failed: %s" % str( e ) )
+ # move the current state of all existing requests to the request_event table
+ cmd = \
+ "INSERT INTO request_event " + \
+ "SELECT %s AS id," + \
+ "%s AS create_time," + \
+ "%s AS update_time," + \
+ "request.id AS request_id," + \
+ "request.state AS state " + \
+ "FROM request;"
+ cmd = cmd % ( nextval('request_event'), localtimestamp(), localtimestamp() )
+ db_session.execute( cmd )
+
+ # Delete the state column
+ try:
+ Request_table = Table( "request", metadata, autoload=True )
+ except NoSuchTableError:
+ Request_table = None
+ log.debug( "Failed loading table request" )
+ if Request_table:
+ try:
+ Request_table.c.state.drop()
+ except Exception, e:
+ log.debug( "Deleting column 'state' to request table failed: %s" % ( str( e ) ) )
+
+def downgrade():
+ pass
\ No newline at end of file
diff --git a/lib/galaxy/web/controllers/requests.py b/lib/galaxy/web/controllers/requests.py
index 75afe8ae6df..f86ea5d403f 100644
--- a/lib/galaxy/web/controllers/requests.py
+++ b/lib/galaxy/web/controllers/requests.py
@@ -28,27 +28,57 @@ class RequestsGrid( grids.Grid ):
return request.type.name
class LastUpdateColumn( grids.TextColumn ):
def get_value(self, trans, grid, request):
- return request.update_time
+ delta = datetime.utcnow() - request.update_time
+ if delta > timedelta( minutes=60 ):
+ last_update = '%s hours' % int( delta.seconds / 60 / 60 )
+ else:
+ last_update = '%s minutes' % int( delta.seconds / 60 )
+ return last_update
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 '
%s
' % request.state()
+ elif request.state() == request.states.NEW:
+ return '%s
' % request.state()
+ elif request.state() == request.states.SUBMITTED:
+ return '%s
' % request.state()
+ elif request.state() == request.states.COMPLETE:
+ return '%s
' % request.state()
+ return request.state()
def filter( self, db_session, query, column_filter ):
""" Modify query to filter request by state. """
if column_filter == "All":
return query
if column_filter:
- query = query.filter( model.Request.state == column_filter )
- return query
+ # 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.UNSUBMITTED,
- 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
+ """ 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 DeletedColumn( grids.GridColumn ):
def get_accepted_filters( self ):
""" Returns a list of accepted filters for this column. """
@@ -66,7 +96,7 @@ class RequestsGrid( grids.Grid ):
num_rows_per_page = 50
preserve_state = True
use_paging = True
- default_filter = dict( deleted="False", state=model.Request.states.UNSUBMITTED)
+ default_filter = dict( deleted="False", state=model.Request.states.NEW)
columns = [
NameColumn( "Name",
key="name",
@@ -83,23 +113,27 @@ class RequestsGrid( grids.Grid ):
TypeColumn( "Type" ),
LastUpdateColumn( "Last update",
format=time_ago ),
- StateColumn( "State",
- key='state',
- filterable="advanced"),
DeletedColumn( "Deleted",
key="deleted",
- visible=True,
- filterable="advanced" )
+ 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] ],
+ 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 ) ),
+ 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.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 ) )
]
@@ -145,9 +179,23 @@ class Requests( BaseController ):
return self.__undelete_request( trans, **kwd )
elif operation == "edit":
return self.__edit_request( trans, **kwd )
+ elif operation == "events":
+ return self.__request_events( trans, **kwd )
+ # if there are one or more requests that has been rejected by the admin
+ # recently, then show a msg 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 )
+ rejected = 0
+ for r in rlist:
+ if r.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
# Render the list view
return self.request_grid( trans, **kwd )
-
def __show_request(self, trans, **kwd):
params = util.Params( kwd )
msg = util.restore_text( params.get( 'msg', '' ) )
@@ -172,6 +220,28 @@ class Requests( BaseController ):
sample_copy=self.__copy_sample(current_samples),
details='hide', edit_mode='False',
msg=msg, messagetype=messagetype )
+ def __request_events(self, trans, **kwd):
+ try:
+ request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
+ except:
+ msg = "Invalid request ID"
+ log.warn( msg )
+ return trans.response.send_redirect( web.url_for( controller='requests',
+ action='list',
+ status='error',
+ message=msg,
+ **kwd) )
+ events_list = []
+ all_events = request.events
+ for event in all_events:
+ delta = datetime.utcnow() - event.update_time
+ if delta > timedelta( minutes=60 ):
+ last_update = '%s hours' % int( delta.seconds / 60 / 60 )
+ else:
+ last_update = '%s minutes' % int( delta.seconds / 60 )
+ events_list.append((event.state, last_update, event.comment))
+ return trans.fill_template( '/requests/events.mako',
+ events_list=events_list, request=request)
def request_details(self, trans, id):
'''
Shows the request details
@@ -187,7 +257,7 @@ class Requests( BaseController ):
value=request.type.name,
helptext=''))
request_details.append(dict(label='State',
- value=request.state,
+ value=request.state(),
helptext=''))
request_details.append(dict(label='Date created',
value=request.create_time,
@@ -366,9 +436,9 @@ class Requests( BaseController ):
trans.sa_session.add( sample )
trans.sa_session.flush()
return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- operation='show_request',
- id=trans.security.encode_id(request.id)) )
+ action='list',
+ operation='show_request',
+ id=trans.security.encode_id(request.id)) )
elif params.get('edit_samples_button', False) == 'Edit samples':
edit_mode = 'True'
return trans.fill_template( '/requests/show_request.mako',
@@ -700,10 +770,15 @@ class Requests( BaseController ):
if not request:
request = trans.app.model.Request(name, desc, request_type,
trans.user, form_values,
- library=library, folder=folder,
- state=trans.app.model.Request.states.UNSUBMITTED)
+ library=library, folder=folder)
trans.sa_session.add( request )
trans.sa_session.flush()
+ trans.sa_session.refresh( request )
+ # create an event with state 'New' for this new request
+ comments = "Request created."
+ event = trans.app.model.RequestEvent(request, request.states.NEW, comments)
+ trans.sa_session.add( event )
+ trans.sa_session.flush()
else:
request.name = name
request.desc = desc
@@ -712,7 +787,6 @@ class Requests( BaseController ):
request.values = form_values
request.library = library
request.folder = folder
- request.state = trans.app.model.Request.states.UNSUBMITTED
trans.sa_session.add( request )
trans.sa_session.flush()
return request
@@ -731,7 +805,7 @@ class Requests( BaseController ):
message="Invalid request ID",
**kwd) )
if params.get('show', False) == 'True':
- return self.__edit_request(trans, **kwd)
+ return self.__edit_request(trans, id=trans.security.encode_id(request.id), **kwd)
elif params.get('save_changes_request_button', False) == 'Save changes' \
or params.get('edit_samples_button', False) == 'Edit samples':
request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
@@ -760,7 +834,7 @@ class Requests( BaseController ):
messagetype='done',
**new_kwd) )
elif params.get('refresh', False) == 'true':
- return self.__edit_request(trans, **kwd)
+ return self.__edit_request(trans, id=trans.security.encode_id(request.id), **kwd)
def __edit_request(self, trans, **kwd):
try:
@@ -819,7 +893,7 @@ class Requests( BaseController ):
message=msg,
**kwd) )
# a request cannot be deleted once its submitted
- if not request.unsubmitted():
+ if not request.new():
delete_failed.append(request.name)
else:
request.deleted = True
@@ -870,21 +944,22 @@ class Requests( BaseController ):
msg = self.__validate(trans, request)
if msg:
return trans.response.send_redirect( web.url_for( controller='requests',
- action='edit',
+ action='list',
+ operation='edit',
messagetype = 'error',
msg=msg,
- request_id=request.id,
- show='True') )
+ id=trans.security.encode_id(request.id) ))
+ # change the request state to 'Submitted'
+ comments = "Request moved to 'Submitted' state."
+ event = trans.app.model.RequestEvent(request, request.states.SUBMITTED, comments)
+ trans.sa_session.add( event )
+ trans.sa_session.flush()
# get the new state
new_state = request.type.states[0]
for s in request.samples:
event = trans.app.model.SampleEvent(s, new_state, 'Samples submitted to the system')
trans.sa_session.add( event )
trans.sa_session.flush()
- # change request's submitted field
- request.state = request.states.SUBMITTED
- trans.sa_session.add( request )
- trans.sa_session.flush()
return trans.response.send_redirect( web.url_for( controller='requests',
action='list',
id=trans.security.encode_id(request.id),
diff --git a/lib/galaxy/web/controllers/requests_admin.py b/lib/galaxy/web/controllers/requests_admin.py
index a11ca5f0e85..5cea7402db6 100644
--- a/lib/galaxy/web/controllers/requests_admin.py
+++ b/lib/galaxy/web/controllers/requests_admin.py
@@ -8,6 +8,8 @@ import logging, tempfile, zipfile, tarfile, os, sys
from galaxy.web.form_builder import *
from datetime import datetime, timedelta
from galaxy.web.controllers.forms import get_all_forms
+from sqlalchemy.sql.expression import func, and_
+from sqlalchemy.sql import select
log = logging.getLogger( __name__ )
@@ -31,27 +33,58 @@ class RequestsGrid( grids.Grid ):
return request.type.name
class LastUpdateColumn( grids.TextColumn ):
def get_value(self, trans, grid, request):
- return request.update_time
+ delta = datetime.utcnow() - request.update_time
+ if delta > timedelta( minutes=60 ):
+ last_update = '%s hours' % int( delta.seconds / 60 / 60 )
+ else:
+ last_update = '%s minutes' % int( delta.seconds / 60 )
+ return last_update
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 '%s
' % request.state()
+ elif request.state() == request.states.NEW:
+ return '%s
' % request.state()
+ elif request.state() == request.states.SUBMITTED:
+ return '%s
' % request.state()
+ elif request.state() == request.states.COMPLETE:
+ return '%s
' % request.state()
+ return request.state()
def filter( self, db_session, query, column_filter ):
""" Modify query to filter request by state. """
if column_filter == "All":
return query
if column_filter:
- query = query.filter( model.Request.state == column_filter )
- return query
+ # 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)))
+ #print column_filter, q
+ return q
def get_accepted_filters( self ):
- """ Returns a list of accepted filters for this column. """
- accepted_filter_labels_and_vals = [ model.Request.states.UNSUBMITTED,
- 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
+ """ 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
@@ -86,20 +119,25 @@ class RequestsGrid( grids.Grid ):
filterable="advanced" ),
SamplesColumn( "Sample(s)",
link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ), ),
- TypeColumn( "Type" ),
+ TypeColumn( "Type",
+ link=( lambda item: iff( item.deleted, None, dict( operation="view_type", id=item.type.id ) ) ), ),
LastUpdateColumn( "Last update",
format=time_ago ),
- StateColumn( "State",
- key='state',
- filterable="advanced"),
- UserColumn( "User",
- key='user.email',
- model_class=model.Request,
- filterable="advanced" ),
DeletedColumn( "Deleted",
key="deleted",
- visible=True,
- filterable="advanced" )
+ 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 ) ) ),
+ ),
+ UserColumn( "User",
+ #key='user.email',
+ model_class=model.Request)
+
]
columns.append( grids.MulticolFilterColumn( "Search",
cols_to_filter=[ columns[0], columns[1], columns[6] ],
@@ -107,10 +145,11 @@ class RequestsGrid( grids.Grid ):
visible=False,
filterable="standard" ) )
operations = [
- grids.GridOperation( "Submit", allow_multiple=False, condition=( lambda item: not item.deleted and item.unsubmitted() and item.samples ) ),
+ 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 and item.unsubmitted() ) ),
+ grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: not item.deleted and item.new() ) ),
grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ),
]
global_actions = [
@@ -227,6 +266,11 @@ class Requests( BaseController ):
return self.__edit_request( trans, **kwd )
elif operation == "reject":
return self.__reject_request( trans, **kwd )
+ elif operation == "events":
+ return self.__request_events( trans, **kwd )
+ elif operation == "view_type":
+ return self.__view_request_type( trans, **kwd )
+
# Render the grid view
return self.request_grid( trans, **kwd )
def __show_request(self, trans, **kwd):
@@ -253,7 +297,52 @@ class Requests( BaseController ):
sample_copy=self.__copy_sample(current_samples),
details='hide', edit_mode='False',
msg=msg, messagetype=messagetype )
-
+ @web.expose
+ @web.require_admin
+ def edit(self, trans, **kwd):
+ params = util.Params( kwd )
+ msg = util.restore_text( params.get( 'msg', '' ) )
+ messagetype = params.get( 'messagetype', 'done' )
+ try:
+ request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', None ) ) )
+ except:
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='list',
+ status='error',
+ message="Invalid request ID",
+ **kwd) )
+ if params.get('show', False) == 'True':
+ return self.__edit_request(trans, id=trans.security.encode_id(request.id), **kwd)
+ elif params.get('save_changes_request_button', False) == 'Save changes' \
+ or params.get('edit_samples_button', False) == 'Edit 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', '')):
+ msg = 'Please enter the Name of the request'
+ kwd['messagetype'] = 'error'
+ kwd['msg'] = msg
+ kwd['show'] = 'True'
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='edit',
+ **kwd) )
+ request = self.__save_request(trans, request, **kwd)
+ msg = 'The changes made to the request named %s has been saved' % request.name
+ if params.get('save_changes_request_button', False) == 'Save changes':
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='list',
+ message=msg ,
+ status='done') )
+ elif params.get('edit_samples_button', False) == 'Edit samples':
+ new_kwd = {}
+ new_kwd['request_id'] = request.id
+ new_kwd['edit_samples_button'] = 'Edit samples'
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='show_request',
+ msg=msg ,
+ messagetype='done',
+ **new_kwd) )
+ elif params.get('refresh', False) == 'true':
+ return self.__edit_request(trans, id=trans.security.encode_id(request.id), **kwd)
+
def __edit_request(self, trans, **kwd):
try:
request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
@@ -311,7 +400,7 @@ class Requests( BaseController ):
message=msg,
**kwd) )
# a request cannot be deleted once its submitted
- if not request.unsubmitted():
+ if not request.new():
delete_failed.append(request.name)
else:
request.deleted = True
@@ -362,18 +451,24 @@ class Requests( BaseController ):
msg = self.__validate(trans, request)
if msg:
return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='edit',
+ action='list',
+ operation='edit',
messagetype = 'error',
msg=msg,
- request_id=request.id,
- show='True') )
- # get the new state
+ id=trans.security.encode_id(request.id) ) )
+ # change the request state to 'Submitted'
+ if request.user.email is not trans.user:
+ comments = "Request moved to 'Submitted' state 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)
+ 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 submitted to the system')
trans.sa_session.add( event )
- # change request's submitted field
- request.state = request.states.SUBMITTED
trans.sa_session.add( request )
trans.sa_session.flush()
return trans.response.send_redirect( web.url_for( controller='requests_admin',
@@ -393,15 +488,64 @@ class Requests( BaseController ):
status='error',
message=msg,
**kwd) )
- # change request's submitted field
- request.state = request.states.UNSUBMITTED
- trans.sa_session.add( request )
+ return trans.fill_template( '/admin/requests/reject.mako',
+ request=request)
+ @web.expose
+ @web.require_admin
+ 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']))
+ try:
+ request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
+ except:
+ msg = "Invalid request ID"
+ log.warn( msg )
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='list',
+ status='error',
+ message=msg,
+ **kwd) )
+ # validate
+ if not params.get('comment', ''):
+ return trans.fill_template( '/admin/requests/reject.mako',
+ request=request, messagetype='error',
+ msg='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)
+ trans.sa_session.add( event )
trans.sa_session.flush()
return trans.response.send_redirect( web.url_for( controller='requests_admin',
action='list',
status='done',
- message='The request %s is now unsubmitted.' % request.name
- ) )
+ message='Request %s has been rejected.' % request.name) )
+
+ def __request_events(self, trans, **kwd):
+ try:
+ request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
+ except:
+ msg = "Invalid request ID"
+ log.warn( msg )
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='list',
+ status='error',
+ message=msg,
+ **kwd) )
+ events_list = []
+ all_events = request.events
+ for event in all_events:
+ delta = datetime.utcnow() - event.update_time
+ if delta > timedelta( minutes=60 ):
+ last_update = '%s hours' % int( delta.seconds / 60 / 60 )
+ else:
+ last_update = '%s minutes' % int( delta.seconds / 60 )
+ events_list.append((event.state, last_update, event.comment))
+ return trans.fill_template( '/admin/requests/events.mako',
+ events_list=events_list, request=request)
#
#---- Request Creation ----------------------------------------------------------
#
@@ -732,9 +876,18 @@ class Requests( BaseController ):
if not request:
request = trans.app.model.Request(name, desc, request_type,
user, form_values,
- library=library, folder=folder,
- state=trans.app.model.Request.states.UNSUBMITTED)
+ library=library, folder=folder)
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)
+ else:
+ comments = "Request created."
+ event = trans.app.model.RequestEvent(request, request.states.NEW, comments)
+ trans.sa_session.add( event )
+ trans.sa_session.flush()
else:
request.name = name
request.desc = desc
@@ -744,58 +897,14 @@ class Requests( BaseController ):
request.library = library
request.folder = folder
trans.sa_session.add( request )
- trans.sa_session.flush()
+ trans.sa_session.flush()
+
return request
#
-#---- Request Editing ----------------------------------------------------------
+#---- Request Page ----------------------------------------------------------
#
- @web.expose
- @web.require_admin
- def edit(self, trans, **kwd):
- params = util.Params( kwd )
- msg = util.restore_text( params.get( 'msg', '' ) )
- messagetype = params.get( 'messagetype', 'done' )
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', None ) ) )
- except:
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status='error',
- message="Invalid request ID",
- **kwd) )
- if params.get('show', False) == 'True':
- return self.__edit_request(trans, request.id, **kwd)
- elif params.get('save_changes_request_button', False) == 'Save changes' \
- or params.get('edit_samples_button', False) == 'Edit 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', '')):
- msg = 'Please enter the Name of the request'
- kwd['messagetype'] = 'error'
- kwd['msg'] = msg
- kwd['show'] = 'True'
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='edit',
- **kwd) )
- request = self.__save_request(trans, request, **kwd)
- msg = 'The changes made to the request named %s has been saved' % request.name
- if params.get('save_changes_request_button', False) == 'Save changes':
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- message=msg ,
- status='done') )
- elif params.get('edit_samples_button', False) == 'Edit samples':
- new_kwd = {}
- new_kwd['request_id'] = request.id
- new_kwd['edit_samples_button'] = 'Edit samples'
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='show_request',
- msg=msg ,
- messagetype='done',
- **new_kwd) )
- elif params.get('refresh', False) == 'true':
- return self.__edit_request(trans, request.id, **kwd)
def __update_samples(self, request, **kwd):
'''
This method retrieves all the user entered sample information and
@@ -1013,7 +1122,7 @@ class Requests( BaseController ):
value=request.type.name,
helptext=''))
request_details.append(dict(label='State',
- value=request.state,
+ value=request.state(),
helptext=''))
request_details.append(dict(label='Date created',
value=request.create_time,
@@ -1167,11 +1276,13 @@ class Requests( BaseController ):
if s.current_state().id != request.type.states[-1].id:
complete = False
if complete:
- request.state = request.states.COMPLETE
- else:
- request.state = request.states.SUBMITTED
- trans.sa_session.add( request )
- trans.sa_session.flush()
+ # change the request state to 'Complete'
+ comments = "All samples of this request are in the last sample state (%s)." % request.type.states[-1].name
+ event = trans.app.model.RequestEvent(request, request.states.COMPLETE, comments)
+ trans.sa_session.add( event )
+ trans.sa_session.flush()
+# trans.sa_session.add( request )
+# trans.sa_session.flush()
def change_state(self, trans, sample):
possible_states = sample.request.type.states
curr_state = sample.current_state()
diff --git a/lib/galaxy/web/framework/helpers/grids.py b/lib/galaxy/web/framework/helpers/grids.py
index f0107dd7f17..173f25f0e4d 100644
--- a/lib/galaxy/web/framework/helpers/grids.py
+++ b/lib/galaxy/web/framework/helpers/grids.py
@@ -402,7 +402,7 @@ class MulticolFilterColumn( TextColumn ):
return query.filter( complete_filter )
class GridOperation( object ):
- def __init__( self, label, key=None, condition=None, allow_multiple=True, allow_popup=True, target=None, url_args=None, async_compatible=False ):
+ def __init__( self, label, key=None, condition=None, allow_multiple=True, allow_popup=True, target=None, url_args=None, async_compatible=False, confirm=None ):
self.label = label
self.key = key
self.allow_multiple = allow_multiple
@@ -411,6 +411,8 @@ class GridOperation( object ):
self.target = target
self.url_args = url_args
self.async_compatible = async_compatible
+ # if 'confirm' is set, then ask before completing the operation
+ self.confirm = confirm
def get_url_args( self, item ):
if self.url_args:
temp = dict( self.url_args )
diff --git a/templates/admin/requests/events.mako b/templates/admin/requests/events.mako
new file mode 100644
index 00000000000..0d1c8ed9546
--- /dev/null
+++ b/templates/admin/requests/events.mako
@@ -0,0 +1,36 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+History of Sequencing Request "${request.name}"
+
+User: ${request.user.email}
+
+%if msg:
+ ${render_msg( msg, messagetype )}
+%endif
+
+
diff --git a/templates/admin/requests/reject.mako b/templates/admin/requests/reject.mako
new file mode 100644
index 00000000000..8dfecb2b8bf
--- /dev/null
+++ b/templates/admin/requests/reject.mako
@@ -0,0 +1,42 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+%if msg:
+ ${render_msg( msg, messagetype )}
+%endif
+
+Reject Sequencing Request "${request.name}"
+
+User: ${request.user.email}
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/admin/requests/show_request.mako b/templates/admin/requests/show_request.mako
index aee34456561..0a6fa2c6ddf 100644
--- a/templates/admin/requests/show_request.mako
+++ b/templates/admin/requests/show_request.mako
@@ -5,11 +5,16 @@
${render_msg( msg, messagetype )}
%endif
+%if request.rejected():
+ ${render_msg( "Reason for rejection: "+request.last_comment(), "warning" )}
+%endif
+
@@ -82,7 +91,7 @@
%if grid_index == 0:
${sample.bar_code} |
- %if sample.request.unsubmitted():
+ %if sample.request.new():
Unsubmitted
%else:
${sample.current_state().name}
@@ -117,6 +126,8 @@
%else:
None
%endif
+ %elif rd['label'] == 'State':
+ ${rd['value']}
%else:
${rd['value']}
%endif
diff --git a/templates/grid_base.mako b/templates/grid_base.mako
index c9ea31a4248..5d1d4441d16 100644
--- a/templates/grid_base.mako
+++ b/templates/grid_base.mako
@@ -860,7 +860,11 @@ ${self.render_grid_table()}
if operation.target:
target = "target='" + operation.target + "'"
%>
- ${operation.label}
+ %if operation.confirm:
+ ${operation.label}
+ %else:
+ ${operation.label}
+ %endif
%endif
%endfor
diff --git a/templates/requests/events.mako b/templates/requests/events.mako
new file mode 100644
index 00000000000..f2bfbccec90
--- /dev/null
+++ b/templates/requests/events.mako
@@ -0,0 +1,39 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+History of Sequencing Request "${request.name}"
+
+
+%if msg:
+ ${render_msg( msg, messagetype )}
+%endif
+
+
diff --git a/templates/requests/show_request.mako b/templates/requests/show_request.mako
index 58a570e5f6d..3487e608994 100644
--- a/templates/requests/show_request.mako
+++ b/templates/requests/show_request.mako
@@ -6,6 +6,9 @@
${render_msg( msg, messagetype )}
%endif
+%if request.rejected():
+ ${render_msg( "Reason for rejection: "+request.last_comment(), "warning" )}
+%endif
|
%if grid_index == 0:
- %if sample.request.unsubmitted():
+ %if sample.request.new():
Unsubmitted
%else:
${sample.current_state().name}
@@ -105,9 +112,11 @@
%if rd['label'] == 'Data library':
%if rd['value']:
${rd['value']}
- %else:
- None
- %endif
+ %elif rd['label'] == 'State':
+ ${rd['value']}
+ %else:
+ None
+ %endif
%else:
${rd['value']}
%endif
diff --git a/templates/sample/sample_events.mako b/templates/sample/sample_events.mako
index c6a89d26638..72ba4d1b416 100644
--- a/templates/sample/sample_events.mako
+++ b/templates/sample/sample_events.mako
@@ -21,24 +21,25 @@
${render_msg( msg, messagetype )}
%endif
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/test/base/twilltestcase.py b/test/base/twilltestcase.py
index 2b9d6947bb9..288ada8afb8 100644
--- a/test/base/twilltestcase.py
+++ b/test/base/twilltestcase.py
@@ -1298,10 +1298,15 @@ class TwillTestCase( unittest.TestCase ):
self.home()
self.visit_url( "%s/requests_admin/list?operation=Submit&id=%s" % ( self.url, self.security.encode_id( request_id ) ))
self.check_page_for_string( 'The request %s has been submitted.' % request_name )
- def reject_request( self, request_id, request_name ):
+ def reject_request( self, request_id, request_name, comment ):
self.home()
self.visit_url( "%s/requests_admin/list?operation=Reject&id=%s" % ( self.url, self.security.encode_id( request_id ) ))
- self.check_page_for_string( 'The request %s is now unsubmitted.' % request_name )
+ self.check_page_for_string( 'Reject Sequencing Request "%s"' % request_name )
+ tc.fv( "1", "comment", comment )
+ tc.submit( "reject_button" )
+ self.check_page_for_string( 'Request %s has been rejected.' % request_name )
+ self.visit_url( "%s/requests/list?sort=-create_time&operation=show_request&id=%s" % ( self.url, self.security.encode_id( request_id ) ))
+ self.check_page_for_string( comment )
def add_bar_codes( self, request_id, request_name, bar_codes ):
self.home()
self.visit_url( "%s/requests_admin/bar_codes?request_id=%i" % (self.url, request_id) )
diff --git a/test/functional/test_forms_and_requests.py b/test/functional/test_forms_and_requests.py
index cf13c6872a6..5bb299a157e 100644
--- a/test/functional/test_forms_and_requests.py
+++ b/test/functional/test_forms_and_requests.py
@@ -194,9 +194,9 @@ class TestFormsAndRequests( TwillTestCase ):
.filter( and_( galaxy.model.Request.table.c.name==request_name,
galaxy.model.Request.table.c.deleted==False ) ) \
.first()
- # check if the request's state is now set to 'unsubmitted'
- assert request_one.state is not request_one.states.UNSUBMITTED, "The state of the request '%s' should be set to '%s'" \
- % ( request_one.name, request_one.states.UNSUBMITTED )
+ # check if the request's state is now set to 'new'
+ assert request_one.state is not request_one.states.NEW, "The state of the request '%s' should be set to '%s'" \
+ % ( request_one.name, request_one.states.NEW )
# sample fields
samples = [ ( 'Sample One', [ 'S1 Field 0 Value' ] ),
( 'Sample Two', [ 'S2 Field 0 Value' ] ) ]
@@ -207,8 +207,8 @@ class TestFormsAndRequests( TwillTestCase ):
self.edit_request(request_one.id, request_one.name, request_one.name+' (Renamed)',
request_one.desc+' (Re-described)', library_one.id, folder_one.id, fields)
sa_session.refresh( request_one )
- # check if the request is showing in the 'unsubmitted' filter
- self.check_request_grid(state='Unsubmitted', request_name=request_one.name)
+ # check if the request is showing in the 'new' filter
+ self.check_request_grid(state='New', request_name=request_one.name)
# submit the request
self.submit_request( request_one.id, request_one.name )
sa_session.refresh( request_one )
@@ -257,11 +257,11 @@ class TestFormsAndRequests( TwillTestCase ):
.filter( and_( galaxy.model.Request.table.c.name==request_name,
galaxy.model.Request.table.c.deleted==False ) ) \
.first()
- # check if the request is showing in the 'unsubmitted' filter
- self.check_request_admin_grid(state='Unsubmitted', request_name=request_two.name)
- # check if the request's state is now set to 'unsubmitted'
- assert request_two.state is not request_two.states.UNSUBMITTED, "The state of the request '%s' should be set to '%s'" \
- % ( request_two.name, request_two.states.UNSUBMITTED )
+ # check if the request is showing in the 'new' filter
+ self.check_request_admin_grid(state='New', request_name=request_two.name)
+ # check if the request's state is now set to 'new'
+ assert request_two.state is not request_two.states.NEW, "The state of the request '%s' should be set to '%s'" \
+ % ( request_two.name, request_two.states.NEW )
# sample fields
samples = [ ( 'Sample One', [ 'S1 Field 0 Value' ] ),
( 'Sample Two', [ 'S2 Field 0 Value' ] ) ]
@@ -282,10 +282,10 @@ class TestFormsAndRequests( TwillTestCase ):
'''Testing rejecting a request'''
self.logout()
self.login( email='test@bx.psu.edu' )
- self.reject_request( request_two.id, request_two.name )
+ self.reject_request( request_two.id, request_two.name, "Rejection test comment" )
sa_session.refresh( request_two )
- # check if the request is showing in the 'unsubmitted' filter
- self.check_request_admin_grid(state='Unsubmitted', request_name=request_two.name)
+ # check if the request is showing in the 'rejected' filter
+ self.check_request_admin_grid(state='Rejected', request_name=request_two.name)
# check if the request's state is now set to 'submitted'
- assert request_two.state is not request_two.states.UNSUBMITTED, "The state of the request '%s' should be set to '%s'" \
- % ( request_two.name, request_two.states.UNSUBMITTED )
+ assert request_two.state is not request_two.states.REJECTED, "The state of the request '%s' should be set to '%s'" \
+ % ( request_two.name, request_two.states.REJECTED )
|