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.core.exceptions import ObjectDoesNotExist
13 from django.core.mail import send_mail, mail_admins
14 from django.http import HttpResponse, Http404
16 from htsworkflow.frontend.auth import require_api_key
17 from htsworkflow.frontend import settings
18 from htsworkflow.frontend.experiments.models import FlowCell, DataRun, Lane
19 from htsworkflow.frontend.samples.models import Library, HTSUser
21 def flowcell_information(flowcell_id):
23 Return a dictionary describing a flowcell
26 fc = FlowCell.objects.get(flowcell_id__startswith=flowcell_id)
27 except FlowCell.DoesNotExist, e:
31 for lane in fc.lane_set.all():
32 lane_set[lane.lane_number] = {
33 'cluster_estimate': lane.cluster_estimate,
34 'comment': lane.comment,
35 'experiment_type': lane.library.experiment_type.name,
36 'experiment_type_id': lane.library.experiment_type_id,
37 'flowcell': lane.flowcell.flowcell_id,
38 'lane_number': int(lane.lane_number),
39 'library_name': lane.library.library_name,
40 'library_id': lane.library.id,
41 'library_species': lane.library.library_species.scientific_name,
42 'pM': unicode(lane.pM),
43 'read_length': lane.flowcell.read_length
46 if fc.control_lane is None:
49 control_lane = int(fc.control_lane)
52 'advanced_run': fc.advanced_run,
53 'cluster_station_id': fc.cluster_station_id,
54 'cluster_station': fc.cluster_station.name,
55 'control_lane': control_lane,
56 # 'datarun_set': how should this be represented?,
57 'flowcell_id': fc.flowcell_id,
61 'paired_end': fc.paired_end,
62 'read_length': fc.read_length,
63 'run_date': fc.run_date.isoformat(),
64 'sequencer_id': fc.sequencer_id,
65 'sequencer': fc.sequencer.name,
70 def flowcell_json(request, fc_id):
72 Return a JSON blob containing enough information to generate a config file.
74 require_api_key(request)
76 fc_dict = flowcell_information(fc_id)
81 fc_json = json.dumps(fc_dict)
82 return HttpResponse(fc_json, mimetype = 'application/json')
84 def lanes_for(username=None):
86 Given a user id try to return recent lanes as a list of dictionaries
89 if username is not None:
90 user = HTSUser.objects.get(username=username)
91 query.update({'library__affiliations__users__id': user.id})
93 lanes = Lane.objects.filter(**query).order_by('-flowcell__run_date')
98 affiliations = l.library.affiliations.all()
99 affiliations_list = [(a.id, a.name) for a in affiliations]
100 result.append({ 'flowcell': l.flowcell.flowcell_id,
101 'run_date': l.flowcell.run_date.isoformat(),
102 'lane_number': l.lane_number,
103 'library': l.library.id,
104 'library_name': l.library.library_name,
105 'comment': l.comment,
106 'affiliations': affiliations_list})
109 def lanes_for_json(request, username):
111 Format lanes for a user
113 require_api_key(request)
116 result = lanes_for(username)
117 except ObjectDoesNotExist, e:
120 #convert query set to python structure
122 result_json = json.dumps(result)
123 return HttpResponse(result_json, mimetype='application/json')
125 def updStatus(request):
129 UpdatedStatus = 'unknown'
131 runfolder = 'unknown'
132 ClIP = request.META['REMOTE_ADDR']
134 if hasattr(request, 'user'):
137 #Check access permission
138 if not (user.is_superuser and settings.ALLOWED_IPS.has_key(ClIP)):
139 return HttpResponse("%s access denied from %s." % (user, ClIP))
141 # ~~~~~~Parameters for the job ~~~~
142 if request.REQUEST.has_key('fcid'):
143 fcid = request.REQUEST['fcid']
145 return HttpResponse('missing fcid')
147 if request.REQUEST.has_key('runf'):
148 runfolder = request.REQUEST['runf']
150 return HttpResponse('missing runf')
153 if request.REQUEST.has_key('updst'):
154 UpdatedStatus = request.REQUEST['updst']
156 return HttpResponse('missing status')
158 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
160 # Update Data Run status in DB
161 # Try get rec. If not found return 'entry not found + <fcid><runfolder>', if found try update and return updated
163 rec = DataRun.objects.get(run_folder=runfolder)
164 rec.run_status = UpdatedStatus
166 #if there's a message update that too
167 mytimestamp = datetime.now().__str__()
168 mytimestamp = re.sub(pattern=":[^:]*$",repl="",string=mytimestamp)
169 if request.REQUEST.has_key('msg'):
170 rec.run_note += ", "+request.REQUEST['msg']+" ("+mytimestamp+")"
172 if UpdatedStatus == '1':
173 rec.run_note = "Started ("+mytimestamp+")"
176 output = "Hello "+settings.ALLOWED_IPS[ClIP]+". Updated to:'"+DataRun.RUN_STATUS_CHOICES[int(UpdatedStatus)][1].__str__()+"'"
177 except ObjectDoesNotExist:
178 output = "entry not found: "+fcid+", "+runfolder
181 #Notify researcher by email
183 #send_mail('Exp Tracker', 'Data Run Status '+output, 'rrauch@stanford.edu', ['rrrami@gmail.com'], fail_silently=False)
184 #mail_admins("test subject", "testing , testing", fail_silently=False)
185 # gives error: (49, "Can't assign requested address")
186 return HttpResponse(output)
188 def generateConfile(request,fcid):
190 #ClIP = request.META['REMOTE_ADDR']
191 #if (settings.ALLOWED_IPS.has_key(ClIP)): granted = True
193 #if not granted: return HttpResponse("access denied.")
195 config = ['READ_LENGTH 25']
196 config += ['ANALYSIS eland']
197 config += ['GENOME_FILE all_chr.fa']
198 config += ['ELAND_MULTIPLE_INSTANCES 8']
199 genome_dir = 'GENOME_DIR /Volumes/Genomes/'
200 eland_genome = 'ELAND_GENOME /Volumes/Genomes/'
203 fc = FlowCell.objects.get(flowcell_id=fcid)
204 for lane in fc.lane_set.all():
205 config += [ str(lane.lane_number) +":" + \
206 genome_dir + lane.library.library_species.scientific_name ]
207 config += [ str(lane.lane_number) +":" + \
208 eland_genome + lane.library.library_species.scientific_name ]
210 except ObjectDoesNotExist:
211 config = 'Entry not found for fcid = '+fcid
213 return os.linesep.join(config)
217 ClIP = req.META['REMOTE_ADDR']
218 if (settings.ALLOWED_IPS.has_key(ClIP)): granted = True
220 if not granted: return HttpResponse("access denied. IP: "+ClIP)
223 cnfgfile = 'Nothing found'
224 runfolder = 'unknown'
225 request = req.REQUEST
226 if request.has_key('fcid'):
227 fcid = request['fcid']
228 if request.has_key('runf'):
229 runfolder = request['runf']
231 rec = DataRun.objects.get(run_folder=runfolder) #,flowcell_id=fcid)
232 cnfgfile = rec.config_params
233 #match_str = re.compile(r"READ_LENGTH.+$")
234 match_str = re.compile('^READ_LENGTH.+')
235 if not match_str.search(cnfgfile):
236 cnfgfile = generateConfile(request,fcid)
237 if match_str.search(cnfgfile):
238 rec = DataRun.objects.get(run_folder=runfolder) #,flowcell_id=fcid)
239 rec.config_params = cnfgfile
242 cnfgfile = 'Failed generating config params for RunFolder = '+runfolder +', Flowcell id = '+ fcid+ ' Config Text:\n'+cnfgfile
244 except ObjectDoesNotExist:
245 cnfgfile = 'Entry not found for RunFolder = '+runfolder
247 return HttpResponse(cnfgfile, mimetype='text/plain')
249 def getLaneLibs(req):
251 ClIP = req.META['REMOTE_ADDR']
252 if (settings.ALLOWED_IPS.has_key(ClIP)): granted = True
254 if not granted: return HttpResponse("access denied.")
256 request = req.REQUEST
259 if request.has_key('fcid'):
260 fcid = request['fcid']
262 rec = FlowCell.objects.get(flowcell_id=fcid)
264 year = datetime.today().year.__str__()
265 year = replace(year,'20','')
266 month = datetime.today().month
267 if month < 10: month = "0"+month.__str__()
268 else: month = month.__str__()
269 day = datetime.today().day
270 if day < 10: day = "0"+day.__str__()
271 else: day = day.__str__()
272 mydate = year+month+day
273 outputfile = '<?xml version="1.0" ?>'
274 outputfile += '\n<SolexaResult Date="'+mydate+'" Flowcell="'+fcid+'" Client="'+settings.ALLOWED_IPS[ClIP]+'">'
275 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=""/>'
276 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=""/>'
277 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=""/>'
278 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=""/>'
279 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=""/>'
280 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=""/>'
281 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=""/>'
282 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=""/>'
283 outputfile += '\n</SolexaResult>'
284 except ObjectDoesNotExist:
285 outputfile = 'Flowcell entry not found for: '+fcid
286 else: outputfile = 'Missing input: flowcell id'
288 return HttpResponse(outputfile, mimetype='text/plain')
290 def estimateFlowcellDuration(flowcell):
292 Attempt to estimate how long it will take to run a flowcell
295 # (3600 seconds * 1.5 hours per cycle )
296 sequencing_seconds_per_cycle= 3600 * 1.5
297 # 800 is a rough guess
298 pipeline_seconds_per_cycle = 800
300 cycles = flowcell.read_length
301 if flowcell.paired_end:
303 sequencing_time = timedelta(0, cycles * sequencing_seconds_per_cycle)
304 analysis_time = timedelta(0, cycles * pipeline_seconds_per_cycle)
305 estimate_mid = sequencing_time + analysis_time
309 def estimateFlowcellTimeRemaining(flowcell):
310 estimate_mid = estimateFlowcellDuration(flowcell)
312 # offset for how long we've been running
313 running_time = datetime.now() - flowcell.run_date
314 estimate_mid -= running_time
318 def roundToDays(estimate):
320 Given a time estimate round up and down in days
323 estimate_low = timedelta(estimate.days, 0)
324 # floor estimate_mid and add a day
325 estimate_high = timedelta(estimate.days+1, 0)
327 return (estimate_low, estimate_high)
330 def makeUserLaneMap(flowcell):
332 Given a flowcell return a mapping of users interested in
333 the libraries on those lanes.
337 for lane in flowcell.lane_set.all():
338 for affiliation in lane.library.affiliations.all():
339 for user in affiliation.users.all():
340 users.setdefault(user,[]).append(lane)
344 def getUsersForFlowcell(flowcell):
347 for lane in flowcell.lane_set.all():
348 for affiliation in lane.library.affiliations.all():
349 for user in affiliation.users.all():
354 def makeUserLibraryMap(libraries):
356 Given an interable set of libraries return a mapping or
357 users interested in those libraries.
361 for library in libraries:
362 for affiliation in library.affiliations.all():
363 for user in affiliation.users.all():
364 users.setdefault(user,[]).append(library)
368 def makeAffiliationLaneMap(flowcell):
371 for lane in flowcell.lane_set.all():
372 for affiliation in lane.library.affiliations.all():
373 affs.setdefault(affiliation,[]).append(lane)
377 def makeEmailLaneMap(flowcell):
379 Create a list of email addresses and the lanes associated with those users.
381 The email addresses can come from both the "users" table and the "affiliations" table.
384 for lane in flowcell.lane_set.all():
385 for affiliation in lane.library.affiliations.all():
386 if affiliation.email is not None and len(affiliation.email) > 0:
387 emails.setdefault(affiliation.email,set()).add(lane)
388 for user in affiliation.users.all():
389 if user.email is not None and len(user.email) > 0:
390 emails.setdefault(user.email,set()).add(lane)