1 # some core functions of the exp tracker module
2 from datetime import datetime, timedelta
6 import simplejson as json
11 from django.contrib.auth.decorators import login_required
12 from django.views.decorators.csrf import csrf_exempt
13 from django.core.exceptions import ObjectDoesNotExist
14 from django.core.mail import send_mail, mail_admins
15 from django.http import HttpResponse, Http404
16 from django.conf import settings
18 from htsworkflow.frontend.auth import require_api_key
19 from htsworkflow.frontend.experiments.models import \
24 from htsworkflow.frontend.samples.models import Library, MultiplexIndex, HTSUser
26 def flowcell_information(flowcell_id):
28 Return a dictionary describing a flowcell
31 fc = FlowCell.objects.get(flowcell_id__startswith=flowcell_id)
32 except FlowCell.DoesNotExist, e:
36 for lane in fc.lane_set.all():
38 'cluster_estimate': lane.cluster_estimate,
39 'comment': lane.comment,
40 'experiment_type': lane.library.experiment_type.name,
41 'experiment_type_id': lane.library.experiment_type_id,
42 'flowcell': lane.flowcell.flowcell_id,
43 'lane_number': lane.lane_number,
44 'library_name': lane.library.library_name,
45 'library_id': lane.library.id,
46 'library_species': lane.library.library_species.scientific_name,
47 'pM': unicode(lane.pM),
48 'read_length': lane.flowcell.read_length,
49 'status_code': lane.status,
50 'status': LANE_STATUS_MAP[lane.status]
52 sequences = lane.library.index_sequences()
53 if sequences is not None:
54 lane_item['index_sequence'] = sequences
56 lane_set.setdefault(lane.lane_number,[]).append(lane_item)
58 if fc.control_lane is None:
61 control_lane = int(fc.control_lane)
64 'advanced_run': fc.advanced_run,
65 'cluster_station_id': fc.cluster_station_id,
66 'cluster_station': fc.cluster_station.name,
67 'control_lane': control_lane,
68 # 'datarun_set': how should this be represented?,
69 'flowcell_id': fc.flowcell_id,
73 'paired_end': fc.paired_end,
74 'read_length': fc.read_length,
75 'run_date': fc.run_date.isoformat(),
76 'sequencer_id': fc.sequencer_id,
77 'sequencer': fc.sequencer.name,
83 def flowcell_json(request, fc_id):
85 Return a JSON blob containing enough information to generate a config file.
87 require_api_key(request)
89 fc_dict = flowcell_information(fc_id)
94 fc_json = json.dumps(fc_dict)
95 return HttpResponse(fc_json, mimetype = 'application/json')
97 def lanes_for(username=None):
99 Given a user id try to return recent lanes as a list of dictionaries
102 if username is not None:
103 user = HTSUser.objects.get(username=username)
104 query.update({'library__affiliations__users__id': user.id})
106 lanes = Lane.objects.filter(**query).order_by('-flowcell__run_date')
111 affiliations = l.library.affiliations.all()
112 affiliations_list = [(a.id, a.name) for a in affiliations]
113 result.append({ 'flowcell': l.flowcell.flowcell_id,
114 'run_date': l.flowcell.run_date.isoformat(),
115 'lane_number': l.lane_number,
116 'library': l.library.id,
117 'library_name': l.library.library_name,
118 'comment': l.comment,
119 'affiliations': affiliations_list})
123 def lanes_for_json(request, username):
125 Format lanes for a user
127 require_api_key(request)
130 result = lanes_for(username)
131 except ObjectDoesNotExist, e:
134 #convert query set to python structure
136 result_json = json.dumps(result)
137 return HttpResponse(result_json, mimetype='application/json')
140 def updStatus(request):
144 UpdatedStatus = 'unknown'
146 runfolder = 'unknown'
147 ClIP = request.META['REMOTE_ADDR']
149 if hasattr(request, 'user'):
152 #Check access permission
153 if not (user.is_superuser and settings.ALLOWED_IPS.has_key(ClIP)):
154 return HttpResponse("%s access denied from %s." % (user, ClIP))
156 # ~~~~~~Parameters for the job ~~~~
157 if request.REQUEST.has_key('fcid'):
158 fcid = request.REQUEST['fcid']
160 return HttpResponse('missing fcid')
162 if request.REQUEST.has_key('runf'):
163 runfolder = request.REQUEST['runf']
165 return HttpResponse('missing runf')
168 if request.REQUEST.has_key('updst'):
169 UpdatedStatus = request.REQUEST['updst']
171 return HttpResponse('missing status')
173 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
175 # Update Data Run status in DB
176 # Try get rec. If not found return 'entry not found + <fcid><runfolder>', if found try update and return updated
178 rec = DataRun.objects.get(run_folder=runfolder)
179 rec.run_status = UpdatedStatus
181 #if there's a message update that too
182 mytimestamp = datetime.now().__str__()
183 mytimestamp = re.sub(pattern=":[^:]*$",repl="",string=mytimestamp)
184 if request.REQUEST.has_key('msg'):
185 rec.run_note += ", "+request.REQUEST['msg']+" ("+mytimestamp+")"
187 if UpdatedStatus == '1':
188 rec.run_note = "Started ("+mytimestamp+")"
191 output = "Hello "+settings.ALLOWED_IPS[ClIP]+". Updated to:'"+DataRun.RUN_STATUS_CHOICES[int(UpdatedStatus)][1].__str__()+"'"
192 except ObjectDoesNotExist:
193 output = "entry not found: "+fcid+", "+runfolder
196 #Notify researcher by email
198 #send_mail('Exp Tracker', 'Data Run Status '+output, 'rrauch@stanford.edu', ['rrrami@gmail.com'], fail_silently=False)
199 #mail_admins("test subject", "testing , testing", fail_silently=False)
200 # gives error: (49, "Can't assign requested address")
201 return HttpResponse(output)
203 def generateConfile(request,fcid):
205 #ClIP = request.META['REMOTE_ADDR']
206 #if (settings.ALLOWED_IPS.has_key(ClIP)): granted = True
208 #if not granted: return HttpResponse("access denied.")
210 config = ['READ_LENGTH 25']
211 config += ['ANALYSIS eland']
212 config += ['GENOME_FILE all_chr.fa']
213 config += ['ELAND_MULTIPLE_INSTANCES 8']
214 genome_dir = 'GENOME_DIR /Volumes/Genomes/'
215 eland_genome = 'ELAND_GENOME /Volumes/Genomes/'
218 fc = FlowCell.objects.get(flowcell_id=fcid)
219 for lane in fc.lane_set.all():
220 config += [ str(lane.lane_number) +":" + \
221 genome_dir + lane.library.library_species.scientific_name ]
222 config += [ str(lane.lane_number) +":" + \
223 eland_genome + lane.library.library_species.scientific_name ]
225 except ObjectDoesNotExist:
226 config = 'Entry not found for fcid = '+fcid
228 return os.linesep.join(config)
232 ClIP = req.META['REMOTE_ADDR']
233 if (settings.ALLOWED_IPS.has_key(ClIP)): granted = True
235 if not granted: return HttpResponse("access denied. IP: "+ClIP)
238 cnfgfile = 'Nothing found'
239 runfolder = 'unknown'
240 request = req.REQUEST
241 if request.has_key('fcid'):
242 fcid = request['fcid']
243 if request.has_key('runf'):
244 runfolder = request['runf']
246 rec = DataRun.objects.get(run_folder=runfolder) #,flowcell_id=fcid)
247 cnfgfile = rec.config_params
248 #match_str = re.compile(r"READ_LENGTH.+$")
249 match_str = re.compile('^READ_LENGTH.+')
250 if not match_str.search(cnfgfile):
251 cnfgfile = generateConfile(request,fcid)
252 if match_str.search(cnfgfile):
253 rec = DataRun.objects.get(run_folder=runfolder) #,flowcell_id=fcid)
254 rec.config_params = cnfgfile
257 cnfgfile = 'Failed generating config params for RunFolder = '+runfolder +', Flowcell id = '+ fcid+ ' Config Text:\n'+cnfgfile
259 except ObjectDoesNotExist:
260 cnfgfile = 'Entry not found for RunFolder = '+runfolder
262 return HttpResponse(cnfgfile, mimetype='text/plain')
264 def getLaneLibs(req):
266 ClIP = req.META['REMOTE_ADDR']
267 if (settings.ALLOWED_IPS.has_key(ClIP)): granted = True
269 if not granted: return HttpResponse("access denied.")
271 request = req.REQUEST
274 if request.has_key('fcid'):
275 fcid = request['fcid']
277 rec = FlowCell.objects.get(flowcell_id=fcid)
279 year = datetime.today().year.__str__()
280 year = replace(year,'20','')
281 month = datetime.today().month
282 if month < 10: month = "0"+month.__str__()
283 else: month = month.__str__()
284 day = datetime.today().day
285 if day < 10: day = "0"+day.__str__()
286 else: day = day.__str__()
287 mydate = year+month+day
288 outputfile = '<?xml version="1.0" ?>'
289 outputfile += '\n<SolexaResult Date="'+mydate+'" Flowcell="'+fcid+'" Client="'+settings.ALLOWED_IPS[ClIP]+'">'
290 outputfile += '\n<Lane Index="1" Name="'+rec.lane_1_library.library_name+'" Library="'+rec.lane_1_library.id+'" Genome="'+rec.lane_1_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
291 outputfile += '\n<Lane Index="2" Name="'+rec.lane_2_library.library_name+'" Library="'+rec.lane_2_library.id+'" Genome="'+rec.lane_2_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
292 outputfile += '\n<Lane Index="3" Name="'+rec.lane_3_library.library_name+'" Library="'+rec.lane_3_library.id+'" Genome="'+rec.lane_3_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
293 outputfile += '\n<Lane Index="4" Name="'+rec.lane_4_library.library_name+'" Library="'+rec.lane_4_library.id+'" Genome="'+rec.lane_4_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
294 outputfile += '\n<Lane Index="5" Name="'+rec.lane_5_library.library_name+'" Library="'+rec.lane_5_library.id+'" Genome="'+rec.lane_5_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
295 outputfile += '\n<Lane Index="6" Name="'+rec.lane_6_library.library_name+'" Library="'+rec.lane_6_library.id+'" Genome="'+rec.lane_6_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
296 outputfile += '\n<Lane Index="7" Name="'+rec.lane_7_library.library_name+'" Library="'+rec.lane_7_library.id+'" Genome="'+rec.lane_7_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
297 outputfile += '\n<Lane Index="8" Name="'+rec.lane_8_library.library_name+'" Library="'+rec.lane_8_library.id+'" Genome="'+rec.lane_8_library.library_species.use_genome_build+'" PrimerName="" PrimerSeq=""/>'
298 outputfile += '\n</SolexaResult>'
299 except ObjectDoesNotExist:
300 outputfile = 'Flowcell entry not found for: '+fcid
301 else: outputfile = 'Missing input: flowcell id'
303 return HttpResponse(outputfile, mimetype='text/plain')
305 def estimateFlowcellDuration(flowcell):
307 Attempt to estimate how long it will take to run a flowcell
310 # (3600 seconds * 1.5 hours per cycle )
311 sequencing_seconds_per_cycle= 3600 * 1.5
312 # 800 is a rough guess
313 pipeline_seconds_per_cycle = 800
315 cycles = flowcell.read_length
316 if flowcell.paired_end:
318 sequencing_time = timedelta(0, cycles * sequencing_seconds_per_cycle)
319 analysis_time = timedelta(0, cycles * pipeline_seconds_per_cycle)
320 estimate_mid = sequencing_time + analysis_time
324 def estimateFlowcellTimeRemaining(flowcell):
325 estimate_mid = estimateFlowcellDuration(flowcell)
327 # offset for how long we've been running
328 running_time = datetime.now() - flowcell.run_date
329 estimate_mid -= running_time
333 def roundToDays(estimate):
335 Given a time estimate round up and down in days
338 estimate_low = timedelta(estimate.days, 0)
339 # floor estimate_mid and add a day
340 estimate_high = timedelta(estimate.days+1, 0)
342 return (estimate_low, estimate_high)
345 def makeUserLaneMap(flowcell):
347 Given a flowcell return a mapping of users interested in
348 the libraries on those lanes.
352 for lane in flowcell.lane_set.all():
353 for affiliation in lane.library.affiliations.all():
354 for user in affiliation.users.all():
355 users.setdefault(user,[]).append(lane)
359 def getUsersForFlowcell(flowcell):
362 for lane in flowcell.lane_set.all():
363 for affiliation in lane.library.affiliations.all():
364 for user in affiliation.users.all():
369 def makeUserLibraryMap(libraries):
371 Given an interable set of libraries return a mapping or
372 users interested in those libraries.
376 for library in libraries:
377 for affiliation in library.affiliations.all():
378 for user in affiliation.users.all():
379 users.setdefault(user,[]).append(library)
383 def makeAffiliationLaneMap(flowcell):
386 for lane in flowcell.lane_set.all():
387 for affiliation in lane.library.affiliations.all():
388 affs.setdefault(affiliation,[]).append(lane)
392 def makeEmailLaneMap(flowcell):
394 Create a list of email addresses and the lanes associated with those users.
396 The email addresses can come from both the "users" table and the "affiliations" table.
399 for lane in flowcell.lane_set.all():
400 for affiliation in lane.library.affiliations.all():
401 if affiliation.email is not None and len(affiliation.email) > 0:
402 emails.setdefault(affiliation.email,set()).add(lane)
403 for user in affiliation.users.all():
404 if user.email is not None and len(user.email) > 0:
405 emails.setdefault(user.email,set()).add(lane)