# -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations
#########################################################################
## This is a sample controller
## - index is the default action of any application
## - user is required for authentication and authorization
## - download is for downloading files uploaded in the db (does streaming)
## - call exposes all registered services (none by default)
#########################################################################
import defs
import vidjil_utils
import logging
import gluon.contrib.simplejson, time, datetime
if request.env.http_origin:
response.headers['Access-Control-Allow-Origin'] = request.env.http_origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Access-Control-Max-Age'] = 86400
#########################################################################
##return the default index page for vidjil (redirect to the browser)
def index():
return dict(message=T('hello world'))
#########################################################################
##return the view default/help.html
def help():
return dict(message=T('help i\'m lost'))
def logger():
'''Log to the server'''
res = {"success" : "false",
"message" : "/client/: %s" % request.vars['msg']}
try:
lvl = int(request.vars['lvl'])
except:
lvl = logging.INFO
log.log(lvl, res)
def init_db(force=False):
if (force) or (db(db.auth_user.id > 0).count() == 0) :
for table in db :
table.truncate()
id_first_user=""
## création du premier user
id_first_user=db.auth_user.insert(
password = db.auth_user.password.validate('1234')[0],
email = 'plop@plop.com',
first_name = 'System',
last_name = 'Administrator'
)
## création des groupes de base
id_admin_group=db.auth_group.insert(role='admin')
id_sa_group=db.auth_group.insert(role='user_1')
db.auth_group.insert(role="public")
db.auth_membership.insert(user_id=id_first_user, group_id=id_admin_group)
db.auth_membership.insert(user_id=id_first_user, group_id=id_sa_group)
##création des configs de base
id_config_TRG = db.config.insert(
name = 'TRG',
command = '-c clones -z 100 -R 1 -r 1 -G germline/TRG ',
info = 'default trg config'
)
id_config_IGH = db.config.insert(
name = 'IGH',
command = '-c clones -d -z 100 -R 1 -r 1 -G germline/IGH ',
info = 'default igh config'
)
## permission
## system admin have admin/read/create rights on all patients, groups and configs
auth.add_permission(id_admin_group, 'admin', db.patient, 0)
auth.add_permission(id_admin_group, 'admin', db.auth_group, 0)
auth.add_permission(id_admin_group, 'admin', db.config, 0)
auth.add_permission(id_admin_group, 'read', db.patient, 0)
auth.add_permission(id_admin_group, 'read', db.auth_group, 0)
auth.add_permission(id_admin_group, 'read', db.config, 0)
auth.add_permission(id_admin_group, 'create', db.patient, 0)
auth.add_permission(id_admin_group, 'create', db.auth_group, 0)
auth.add_permission(id_admin_group, 'create', db.config, 0)
auth.add_permission(id_admin_group, 'impersonate', db.auth_user, 0)
def init_from_csv():
if db(db.auth_user.id > 0).count() == 0:
res = {"success" : "true", "message" : "Importing " + defs.DB_BACKUP_FILE}
log.info(res)
try:
db.import_from_csv_file(open(defs.DB_BACKUP_FILE, 'rb'), null='')
# db.scheduler_task.truncate()
# db.scheduler_run.truncate()
except Exception as e:
res = {"success": "false", "message": "!" + str(e)}
log.error(res)
raise
res = {"success" : "true", "message" : "coucou"}
log.info(res)
#########################################################################
## add a scheduller task to run vidjil on a specific sequence file
# need sequence_file_id, config_id
# need patient admin permission
def run_request():
error = ""
##TODO check
if not "sequence_file_id" in request.vars :
error += "id sequence file needed, "
if not "config_id" in request.vars:
error += "id config needed, "
id_config = None
else:
id_config = request.vars["config_id"]
if not auth.can_process_file():
error += "permission needed"
id_patient = db.sequence_file[request.vars["sequence_file_id"]].patient_id
if not auth.can_modify_patient(id_patient) :
error += "you do not have permission to launch process for this patient ("+str(id_patient)+"), "
if id_config:
if not auth.can_use_config(id_config) :
error += "you do not have permission to launch process for this config ("+str(id_config)+"), "
if error == "" :
res = schedule_run(request.vars["sequence_file_id"], request.vars["config_id"])
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
else :
res = {"success" : "false",
"message" : "default/run_request : " + error}
log.error(res)
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
#########################################################################
## return .data file
# need patient, config
# need patient admin or read permission
def get_data():
from subprocess import Popen, PIPE, STDOUT
if not auth.user :
res = {"redirect" : URL('default', 'user', args='login', scheme=True, host=True,
vars=dict(_next=URL('default', 'get_data', scheme=True, host=True,
vars=dict(patient = request.vars["patient"],
config =request.vars["config"]))
)
)}
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
error = ""
if not "patient" in request.vars :
error += "id patient file needed, "
if not "config" in request.vars:
error += "id config needed, "
if not auth.can_view_patient(request.vars["patient"]):
error += "you do not have permission to consult this patient ("+str(request.vars["patient"])+")"
query = db( ( db.fused_file.patient_id == request.vars["patient"] )
& ( db.fused_file.config_id == request.vars["config"] )
).select()
for row in query :
fused_file = defs.DIR_RESULTS+'/'+row.fused_file
sequence_file_list = row.sequence_file_list
if not 'fused_file' in locals():
error += "file not found"
if error == "" :
f = open(fused_file, "r")
data = gluon.contrib.simplejson.loads(f.read())
f.close()
patient_name = vidjil_utils.anon_ids(request.vars["patient"])
config_name = db.config[request.vars["config"]].name
command = db.config[request.vars["config"]].command
data["patient_name"] = patient_name
data["config_name"] = config_name
data["dataFileName"] = patient_name + " (" + config_name + ")"
data["info"] = db.patient(request.vars["patient"]).info
data["samples"]["info"] = []
data["samples"]["timestamp"] = []
for i in range(len(data["samples"]["original_names"])) :
data["samples"]["original_names"][i] = data["samples"]["original_names"][i].split('/')[-1]
data["samples"]["info"].append('')
data["samples"]["timestamp"].append('')
## récupération des infos stockées sur la base de données
query = db( ( db.patient.id == db.sequence_file.patient_id )
& ( db.results_file.sequence_file_id == db.sequence_file.id )
& ( db.patient.id == request.vars["patient"] )
& ( db.results_file.config_id == request.vars["config"] )
).select( orderby=db.sequence_file.id|db.results_file.run_date, groupby=db.sequence_file.id )
for row in query :
filename = row.sequence_file.filename
for i in range(len(data["samples"]["original_names"])) :
data_file = data["samples"]["original_names"][i]
if row.sequence_file.data_file == data_file :
data["samples"]["original_names"][i] = filename
data["samples"]["timestamp"][i] = str(row.sequence_file.sampling_date)
data["samples"]["info"][i] = row.sequence_file.info
data["samples"]["commandline"][i] = command
log.debug("get_data (%s) c%s -> %s" % (request.vars["patient"], request.vars["config"], fused_file))
return gluon.contrib.simplejson.dumps(data, separators=(',',':'))
else :
res = {"success" : "false",
"message" : "get_data (%s) c%s : %s " % (request.vars["patient"], request.vars["config"], error)}
log.error(res)
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
#########################################################################
def get_custom_data():
import time
import vidjil_utils
from subprocess import Popen, PIPE, STDOUT
if not auth.user :
res = {"redirect" : URL('default', 'user', args='login', scheme=True, host=True)} #TODO _next
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
error = ""
if not "custom" in request.vars :
error += "no file selected, "
else:
for id in request.vars["custom"] :
sequence_file_id = db.results_file[id].sequence_file_id
patient_id =db.sequence_file[sequence_file_id].patient_id
if not auth.can_view_patient(patient_id):
error += "you do not have permission to consult this patient ("+str(patient_id)+")"
if error == "" :
data = custom_fuse(request.vars["custom"])
data["dataFileName"] = "Compare patients"
data["info"] = "Compare patients"
data["samples"]["original_names"] = []
data["samples"]["timestamp"] = []
data["samples"]["info"] = []
data["samples"]["commandline"] = []
for id in request.vars["custom"] :
sequence_file_id = db.results_file[id].sequence_file_id
patient_id = db.sequence_file[sequence_file_id].patient_id
config_id = db.results_file[id].config_id
patient_name = vidjil_utils.anon_ids(patient_id)
filename = db.sequence_file[sequence_file_id].filename
data["samples"]["original_names"].append(patient_name + "_" + filename)
data["samples"]["timestamp"].append(str(db.sequence_file[sequence_file_id].sampling_date))
data["samples"]["info"].append(db.sequence_file[sequence_file_id].info)
data["samples"]["commandline"].append(db.config[config_id].command)
return gluon.contrib.simplejson.dumps(data, separators=(',',':'))
else :
res = {"success" : "false",
"message" : "default/get_custom_data : " + error}
log.error(res)
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
#########################################################################
## return .analysis file
# need patient_id, config_id
# need patient admin or read permission
def get_analysis():
error = ""
if not "patient" in request.vars :
error += "id patient file needed, "
if not "config" in request.vars:
error += "id config needed, "
if not auth.can_view_patient(request.vars["patient"]):
error += "you do not have permission to consult this patient ("+str(request.vars["patient"])+")"
## empty analysis file
res = {"samples": {"number": 0,
"original_names": [],
"order": [],
"info_sequence_file" : []
},
"custom": [],
"clusters": [],
"clones" : [],
"tags": {},
"vidjil_json_version" : "2014.09"
}
if "custom" in request.vars :
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
if error == "" :
## récupération des infos se trouvant dans le fichier .analysis
analysis_query = db( (db.analysis_file.patient_id == request.vars["patient"])
& (db.analysis_file.config_id == request.vars["config"] ) )
if not analysis_query.isempty() :
row = analysis_query.select().first()
f = open(defs.DIR_RESULTS+'/'+row.analysis_file, "r")
analysis = gluon.contrib.simplejson.loads(f.read())
f.close()
if 'cluster' in analysis:
res["clusters"] = analysis["cluster"]
if 'clusters' in analysis :
res["clusters"] = analysis["clusters"]
res["clones"] = analysis["clones"]
res["tags"] = analysis["tags"]
res["samples"]= analysis["samples"]
res["info_patient"] = db.patient[request.vars["patient"]].info
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
else :
res = {"success" : "false",
"message" : "default/get_analysis : " + error}
log.error(res)
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
#########################################################################
## upload .analysis file and store it on the database
# need patient_id, config_id, fileToUpload
# need patient admin permission
def save_analysis():
error = ""
if not "patient" in request.vars :
error += "id patient file needed, "
if not "config" in request.vars:
error += "id config needed, "
if not auth.can_modify_patient(request.vars['patient']) :
error += "you do not have permission to save changes on this patient"
if error == "" :
analysis_query = db( (db.analysis_file.patient_id == request.vars['patient'])
& (db.analysis_file.config_id == request.vars['config'] ) )
f = request.vars['fileToUpload']
ts = time.time()
if not analysis_query.isempty() :
analysis_id = analysis_query.select().first().id
db.analysis_file[analysis_id] = dict(analysis_file = db.analysis_file.analysis_file.store(f.file, f.filename),
analyze_date = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
)
else:
analysis_id = db.analysis_file.insert(analysis_file = db.analysis_file.analysis_file.store(f.file, f.filename),
config_id = request.vars['config'],
patient_id = request.vars['patient'],
analyze_date = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
)
patient_name = db.patient[request.vars['patient']].first_name + " " + db.patient[request.vars['patient']].last_name
res = {"success" : "true",
"message" : "%s (%s) c%s: analysis saved" % (patient_name, request.vars['patient'], request.vars['config'])}
log.info(res)
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
else :
res = {"success" : "false",
"message" : error}
log.error(res)
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
#########################################################################
def error():
"""
Custom error handler that returns correct status codes,
adapted from http://www.web2pyslices.com/slice/show/1529/custom-error-routing
"""
code = request.vars.code
request_url = request.vars.request_url
requested_uri = request.vars.requested_uri
ticket = request.vars.ticket
response.status = int(code)
assert(response.status == 500 and request_url != request.url) # avoid infinite loop
ticket_url = '%(ticket)s' % { 'host':request.env.http_host,
'ticket':ticket }
log.error("Server error // %s" % ticket_url)
user_str, x = log.process('', None)
user_str = user_str.replace('<','').replace('>','').strip()
mail.send(to=defs.ADMIN_EMAILS,
subject="[Vidjil] Server error - %s" % user_str,
message="Ticket: %s
At: %s
User: %s" % (ticket_url, requested_uri, user_str))
return "Server error"
def user():
"""
exposes:
http://..../[app]/default/user/login
http://..../[app]/default/user/logout
http://..../[app]/default/user/register
http://..../[app]/default/user/profile
http://..../[app]/default/user/retrieve_password
http://..../[app]/default/user/change_password
http://..../[app]/default/user/manage_users (requires membership in
use @auth.requires_login()
@auth.requires_membership('group name')
@auth.requires_permission('read','table name',record_id)
to decorate functions that need access control
"""
#redirect already logged user
if auth.user and request.args[0] == 'login' :
res = {"redirect" : URL('patient', 'index', scheme=True, host=True)}
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
#only authentified admin user can access register view
if auth.user and request.args[0] == 'register' :
#save admin session (the registering will automatically login the new user in order to initialize its default values)
admin_auth = session.auth
auth.is_logged_in = lambda: False
def post_register(form):
#default values for new user
group_id = db(db.auth_group.role == 'public' ).select()[0].id
db.auth_membership.insert(user_id = auth.user.id, group_id = group_id)
#restore admin session after register
session.auth = admin_auth
auth.user = session.auth.user
auth.settings.register_onaccept = post_register
#redirect to the last added user view
auth.settings.logged_url = URL('user', 'info')
auth.settings.login_next = URL('user', 'info')
return dict(form=auth.register())
#reject others
if request.args[0] == 'register' :
res = {"message": "you need to be admin and logged to add new users"}
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
return dict(form=auth())
def impersonate() :
if auth.is_impersonating() :
stop_impersonate()
if request.vars["id"] != 0 :
log.debug({"success" : "true", "message" : "impersonate >> %s" % request.vars["id"]})
auth.impersonate(request.vars["id"])
log.debug({"success" : "true", "message" : "impersonated"})
res = {"redirect": "reload"}
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
def stop_impersonate() :
import time
if auth.is_impersonating() :
log.debug({"success" : "true", "message" : "impersonate << stop"})
auth.impersonate(0)
# force clean login (default impersonate don't restore everything :/ )
auth.login_user(db.auth_user(auth.user.id))
res = {"redirect" : URL('patient', 'index', scheme=True, host=True)}
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
## TODO make custom download for .data et .analysis
@cache.action()
def download():
"""
allows downloading of uploaded files
http://..../[app]/default/download/[filename]
"""
return response.download(request, db, download_filename=request.vars.filename)
def download_data():
file = "test"
return response.stream( file, chunk_size=4096, filename=request.vars.filename)
#########################################################################