default.py 20.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
# -*- 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)
#########################################################################

12
import defs
13
import vidjil_utils
14
import logging
15

16
import gluon.contrib.simplejson, time, datetime
17
if request.env.http_origin:
18
    response.headers['Access-Control-Allow-Origin'] = request.env.http_origin
19
20
    response.headers['Access-Control-Allow-Credentials'] = 'true'
    response.headers['Access-Control-Max-Age'] = 86400
21

22
#########################################################################
23
##return the default index page for vidjil (redirect to the browser)
24
25
26
def index():
    return dict(message=T('hello world'))

27
28
#########################################################################
##return the view default/help.html
Marc Duez's avatar
Marc Duez committed
29
30
31
def help():
    return dict(message=T('help i\'m lost'))

32

33
34
35
36
37
38
39
40

def logger():
    '''Log to the server'''
    res = {"success" : "false",
           "message" : "/client/: %s" % request.vars['msg']}

    try:
        lvl = int(request.vars['lvl'])
41
    except:
42
43
44
        lvl = logging.INFO
    log.log(lvl, res)

Marc Duez's avatar
Marc Duez committed
45
def init_db(force=False):
46
    if (force) or (db(db.auth_user.id > 0).count() == 0) : 
Marc Duez's avatar
Marc Duez committed
47
48
49
        for table in db :
            table.truncate()
        
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
        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)
Marc Duez's avatar
Marc Duez committed
92
        auth.add_permission(id_admin_group, 'impersonate', db.auth_user, 0)
93
94
95

def init_from_csv():
    if db(db.auth_user.id > 0).count() == 0:
96
97
98
99
100
        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='')
101
102
            # db.scheduler_task.truncate()
            # db.scheduler_run.truncate()
103
104
105
106
107
108
109
        except Exception as e:
            res = {"success": "false", "message": "!" + str(e)}
            log.error(res)
            raise

        res = {"success" : "true", "message" : "coucou"}
        log.info(res)
110

111
#########################################################################
112
## add a scheduller task to run vidjil on a specific sequence file
113
# need sequence_file_id, config_id
114
# need patient admin permission
115
def run_request():
116
117
    error = ""

118
    ##TODO check
119
120
121
122
    if not "sequence_file_id" in request.vars :
        error += "id sequence file needed, "
    if not "config_id" in request.vars:
        error += "id config needed, "
Mathieu Giraud's avatar
Mathieu Giraud committed
123
124
125
        id_config = None
    else:
        id_config = request.vars["config_id"]
126
    if not auth.can_process_file():
127
        error += "permission needed"
128

129
    id_patient = db.sequence_file[request.vars["sequence_file_id"]].patient_id
130

131
    if not auth.can_modify_patient(id_patient) :
132
        error += "you do not have permission to launch process for this patient ("+str(id_patient)+"), "
133

Mathieu Giraud's avatar
Mathieu Giraud committed
134
    if id_config:
135
      if not auth.can_use_config(id_config) :
136
137
        error += "you do not have permission to launch process for this config ("+str(id_config)+"), "

138
    if error == "" :
139
        res = schedule_run(request.vars["sequence_file_id"], request.vars["config_id"])
140
141
142
        return gluon.contrib.simplejson.dumps(res, separators=(',',':'))

    else :
143
        res = {"success" : "false",
144
               "message" : "default/run_request : " + error}
145
        log.error(res)
146
        return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
147
148
149



150
151
#########################################################################
## return .data file
152
153
# need patient, config
# need patient admin or read permission
154
def get_data():
155
    from subprocess import Popen, PIPE, STDOUT
156
    if not auth.user :
157
158
159
160
161
162
163
        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=(',',':'))
164

165
    error = ""
166

167
    if not "patient" in request.vars :
168
        error += "id patient file needed, "
169
    if not "config" in request.vars:
170
        error += "id config needed, "
171
    if not auth.can_view_patient(request.vars["patient"]):
Marc Duez's avatar
Marc Duez committed
172
        error += "you do not have permission to consult this patient ("+str(request.vars["patient"])+")"
173

174
175
    query = db( ( db.fused_file.patient_id == request.vars["patient"] )
               & ( db.fused_file.config_id == request.vars["config"] )
176
               ).select()
177
    for row in query :
178
        fused_file = defs.DIR_RESULTS+'/'+row.fused_file
179
        sequence_file_list = row.sequence_file_list
180

181
182
    if not 'fused_file' in locals():
        error += "file not found"
183

184
    if error == "" :
185

186
        f = open(fused_file, "r")
187
        data = gluon.contrib.simplejson.loads(f.read())
188
        f.close()
189
        
190
        patient_name = vidjil_utils.anon_ids(request.vars["patient"])
191
        config_name = db.config[request.vars["config"]].name
192
        command = db.config[request.vars["config"]].command
193
194
195
196
        
        data["patient_name"] = patient_name
        data["config_name"] = config_name
        data["dataFileName"] = patient_name + " (" + config_name + ")"
197
        data["info"] = db.patient(request.vars["patient"]).info
198
        
199
        data["samples"]["info"] = []
200
201
202
203
204
        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('')
205
206

        ## récupération des infos stockées sur la base de données
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
        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
222
                
223
        log.debug("get_data (%s) c%s -> %s" % (request.vars["patient"], request.vars["config"], fused_file))
224
        return gluon.contrib.simplejson.dumps(data, separators=(',',':'))
225

226
227
    else :
        res = {"success" : "false",
228
               "message" : "get_data (%s) c%s : %s " % (request.vars["patient"], request.vars["config"], error)}
229
        log.error(res)
230
        return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
231
232
233
234
    
#########################################################################
def get_custom_data():
    import time
235
    import vidjil_utils
236
237
238
239
240
241
    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 = ""
242

243
244
245
246
247
248
    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
249
            if not auth.can_view_patient(patient_id):
250
251
252
253
                error += "you do not have permission to consult this patient ("+str(patient_id)+")"
            
    if error == "" :
        data = custom_fuse(request.vars["custom"])
254
        
255
256
        data["dataFileName"] = "Compare patients"
        data["info"] = "Compare patients"
257
258
259
        data["samples"]["original_names"] = []
        data["samples"]["timestamp"] = []
        data["samples"]["info"] = []
260
        data["samples"]["commandline"] = []
261
262
263
264
        
        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
265
            config_id = db.results_file[id].config_id
266
            patient_name = vidjil_utils.anon_ids(patient_id)
267
            filename = db.sequence_file[sequence_file_id].filename
268
            data["samples"]["original_names"].append(patient_name + "_" + filename)
269
270
            data["samples"]["timestamp"].append(str(db.sequence_file[sequence_file_id].sampling_date))
            data["samples"]["info"].append(db.sequence_file[sequence_file_id].info)
271
            data["samples"]["commandline"].append(db.config[config_id].command)
272
273
274
275
276
277
278
279
280

        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=(',',':'))
    
281
282
283
#########################################################################
## return .analysis file
# need patient_id, config_id
284
# need patient admin or read permission
285
286
287
def get_analysis():
    error = ""

288
    if not "patient" in request.vars :
289
        error += "id patient file needed, "
290
    if not "config" in request.vars:
291
        error += "id config needed, "
292
    if not auth.can_view_patient(request.vars["patient"]):
293
        error += "you do not have permission to consult this patient ("+str(request.vars["patient"])+")"
294

295
    ## empty analysis file
296
297
298
299
300
301
    res = {"samples": {"number": 0,
                      "original_names": [],
                      "order": [],
                      "info_sequence_file" : []
                       },
           "custom": [],
302
           "clusters": [],
303
           "clones" : [],
304
           "tags": {},
305
           "vidjil_json_version" : "2014.09"
306
           }
307

308
309
310
    if "custom" in request.vars :
        return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
    
311
    if error == "" :
312

313
        ## récupération des infos se trouvant dans le fichier .analysis
314
315
        analysis_query = db(  (db.analysis_file.patient_id == request.vars["patient"])
                   & (db.analysis_file.config_id == request.vars["config"] )  )
316

317
318
        if not analysis_query.isempty() :
            row = analysis_query.select().first()
319
            f = open(defs.DIR_RESULTS+'/'+row.analysis_file, "r")
320
321
            analysis = gluon.contrib.simplejson.loads(f.read())
            f.close()
322
323
324
325
            if 'cluster' in analysis:
                res["clusters"] = analysis["cluster"]
            if 'clusters' in analysis :
                res["clusters"] = analysis["clusters"]
326
            res["clones"] = analysis["clones"]
327
            res["tags"] = analysis["tags"]
328
            res["samples"]= analysis["samples"]
329

330
        res["info_patient"] = db.patient[request.vars["patient"]].info
331
        return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
332

333
334
335
    else :
        res = {"success" : "false",
               "message" : "default/get_analysis : " + error}
336
        log.error(res)
337
        return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
338
339


340
341
342
#########################################################################
## upload .analysis file and store it on the database
# need patient_id, config_id, fileToUpload
343
# need patient admin permission
344
345
346
def save_analysis():
    error = ""

Marc Duez's avatar
Marc Duez committed
347
    if not "patient" in request.vars :
348
        error += "id patient file needed, "
Marc Duez's avatar
Marc Duez committed
349
    if not "config" in request.vars:
350
        error += "id config needed, "
351
    if not auth.can_modify_patient(request.vars['patient']) :
352
        error += "you do not have permission to save changes on this patient"
353

354
    if error == "" :
Marc Duez's avatar
Marc Duez committed
355
356
        analysis_query = db(  (db.analysis_file.patient_id == request.vars['patient'])
                            & (db.analysis_file.config_id == request.vars['config'] )  )
357

358
        f = request.vars['fileToUpload']
359

360
        ts = time.time()
361
362
        if not analysis_query.isempty() :
            analysis_id = analysis_query.select().first().id
363
364
365
            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')
                                                 )
366
367
        else:

368
            analysis_id = db.analysis_file.insert(analysis_file = db.analysis_file.analysis_file.store(f.file, f.filename),
Marc Duez's avatar
Marc Duez committed
369
370
                                                  config_id = request.vars['config'],
                                                  patient_id = request.vars['patient'],
371
372
                                                  analyze_date = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
                                                  )
373

Marc Duez's avatar
Marc Duez committed
374
        patient_name = db.patient[request.vars['patient']].first_name + " " + db.patient[request.vars['patient']].last_name
375

376
        res = {"success" : "true",
377
               "message" : "%s (%s) c%s: analysis saved" % (patient_name, request.vars['patient'], request.vars['config'])}
378
        log.info(res)
379
380
        return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
    else :
381
382
        res = {"success" : "false",
               "message" : error}
383
        log.error(res)
384
        return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
385
386


387

Vidjil Team's avatar
Vidjil Team committed
388
#########################################################################
389
def error():
Vidjil Team's avatar
Vidjil Team committed
390
391
392
393
    """
    Custom error handler that returns correct status codes,
    adapted from http://www.web2pyslices.com/slice/show/1529/custom-error-routing
    """
394

Vidjil Team's avatar
Vidjil Team committed
395
396
    code = request.vars.code
    request_url = request.vars.request_url
397
    requested_uri = request.vars.requested_uri
Vidjil Team's avatar
Vidjil Team committed
398
    ticket = request.vars.ticket
399
    response.status = int(code)
400

401
    assert(response.status == 500 and request_url != request.url) # avoid infinite loop
Vidjil Team's avatar
Vidjil Team committed
402

403
404
405
    ticket_url = '<a href="https://%(host)s/admin/default/ticket/%(ticket)s">%(ticket)s</a>' % { 'host':request.env.http_host,
                                                                                                 'ticket':ticket }
    log.error("Server error // %s" % ticket_url)
406

407
    user_str, x = log.process('', None)
408
    user_str = user_str.replace('<','').replace('>','').strip()
409

410
411
412
    mail.send(to=defs.ADMIN_EMAILS,
              subject="[Vidjil] Server error - %s" % user_str,
              message="<html>Ticket: %s<br/>At: %s<br />User: %s</html>" % (ticket_url, requested_uri, user_str))
Vidjil Team's avatar
Vidjil Team committed
413
414

    return "Server error"
415

Marc Duez's avatar
Marc Duez committed
416
    
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
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
    """
432

Marc Duez's avatar
Marc Duez committed
433
    #redirect already logged user 
434
    if auth.user and request.args[0] == 'login' :
435
436
        res = {"redirect" : URL('patient', 'index', scheme=True, host=True)}
        return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
Marc Duez's avatar
Marc Duez committed
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
    
    #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
452
453
454
455
456
        
        #redirect to the last added user view
        auth.settings.logged_url = URL('user', 'info')
        auth.settings.login_next = URL('user', 'info')
        
Marc Duez's avatar
Marc Duez committed
457
458
459
460
461
462
463
        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=(',',':'))
    
464
    return dict(form=auth())
465

Marc Duez's avatar
Marc Duez committed
466
467
468
469
def impersonate() :
    if auth.is_impersonating() :
        stop_impersonate()
    if request.vars["id"] != 0 :
470
        log.debug({"success" : "true", "message" : "impersonate >> %s" % request.vars["id"]})
Marc Duez's avatar
Marc Duez committed
471
        auth.impersonate(request.vars["id"]) 
472
        log.debug({"success" : "true", "message" : "impersonated"})
Marc Duez's avatar
Marc Duez committed
473
474
475
476
477
478
    res = {"redirect": "reload"}
    return gluon.contrib.simplejson.dumps(res, separators=(',',':'))

def stop_impersonate() :
    import time
    if auth.is_impersonating() :
479
        log.debug({"success" : "true", "message" : "impersonate << stop"})
Marc Duez's avatar
Marc Duez committed
480
481
482
483
484
485
486
487
488
        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=(',',':'))



489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
## 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)



#########################################################################