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
17 from django.utils import timezone
19 from htsworkflow.frontend.auth import require_api_key
20 from htsworkflow.frontend.experiments.models import \
25 from htsworkflow.frontend.samples.models import Library, MultiplexIndex, HTSUser
27 def flowcell_information(flowcell_id):
29 Return a dictionary describing a flowcell
32 fc = FlowCell.objects.get(flowcell_id__startswith=flowcell_id)
33 except FlowCell.DoesNotExist, e:
37 for lane in fc.lane_set.all():
39 'cluster_estimate': lane.cluster_estimate,
40 'comment': lane.comment,
41 'experiment_type': lane.library.experiment_type.name,
42 'experiment_type_id': lane.library.experiment_type_id,
43 'flowcell': lane.flowcell.flowcell_id,
44 'lane_number': lane.lane_number,
45 'library_name': lane.library.library_name,
46 'library_id': lane.library.id,
47 'library_species': lane.library.library_species.scientific_name,
48 'pM': unicode(lane.pM),
49 'read_length': lane.flowcell.read_length,
50 'status_code': lane.status,
51 'status': LANE_STATUS_MAP[lane.status]
53 sequences = lane.library.index_sequences()
54 if sequences is not None:
55 lane_item['index_sequence'] = sequences
57 lane_set.setdefault(lane.lane_number,[]).append(lane_item)
59 if fc.control_lane is None:
62 control_lane = int(fc.control_lane)
65 'advanced_run': fc.advanced_run,
66 'cluster_station_id': fc.cluster_station_id,
67 'cluster_station': fc.cluster_station.name,
68 'control_lane': control_lane,
69 # 'datarun_set': how should this be represented?,
70 'flowcell_id': fc.flowcell_id,
74 'paired_end': fc.paired_end,
75 'read_length': fc.read_length,
76 'run_date': fc.run_date.isoformat(),
77 'sequencer_id': fc.sequencer_id,
78 'sequencer': fc.sequencer.name,
84 def flowcell_json(request, fc_id):
86 Return a JSON blob containing enough information to generate a config file.
88 require_api_key(request)
90 fc_dict = flowcell_information(fc_id)
95 fc_json = json.dumps(fc_dict)
96 return HttpResponse(fc_json, content_type = 'application/json')
98 def lanes_for(username=None):
100 Given a user id try to return recent lanes as a list of dictionaries
103 if username is not None:
104 user = HTSUser.objects.get(username=username)
105 query.update({'library__affiliations__users__id': user.id})
107 lanes = Lane.objects.filter(**query).order_by('-flowcell__run_date')
112 affiliations = l.library.affiliations.all()
113 affiliations_list = [(a.id, a.name) for a in affiliations]
114 result.append({ 'flowcell': l.flowcell.flowcell_id,
115 'run_date': l.flowcell.run_date.isoformat(),
116 'lane_number': l.lane_number,
117 'library': l.library.id,
118 'library_name': l.library.library_name,
119 'comment': l.comment,
120 'affiliations': affiliations_list})
124 def lanes_for_json(request, username):
126 Format lanes for a user
128 require_api_key(request)
131 result = lanes_for(username)
132 except ObjectDoesNotExist, e:
135 #convert query set to python structure
137 result_json = json.dumps(result)
138 return HttpResponse(result_json, content_type='application/json')
141 def updStatus(request):
145 UpdatedStatus = 'unknown'
147 runfolder = 'unknown'
148 ClIP = request.META['REMOTE_ADDR']
150 if hasattr(request, 'user'):
153 #Check access permission
154 if not (user.is_superuser and settings.ALLOWED_IPS.has_key(ClIP)):
155 return HttpResponse("%s access denied from %s." % (user, ClIP))
157 # ~~~~~~Parameters for the job ~~~~
158 if request.REQUEST.has_key('fcid'):
159 fcid = request.REQUEST['fcid']
161 return HttpResponse('missing fcid')
163 if request.REQUEST.has_key('runf'):
164 runfolder = request.REQUEST['runf']
166 return HttpResponse('missing runf')
169 if request.REQUEST.has_key('updst'):
170 UpdatedStatus = request.REQUEST['updst']
172 return HttpResponse('missing status')
174 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
176 # Update Data Run status in DB
177 # Try get rec. If not found return 'entry not found + <fcid><runfolder>', if found try update and return updated
179 rec = DataRun.objects.get(run_folder=runfolder)
180 rec.run_status = UpdatedStatus
182 #if there's a message update that too
183 mytimestamp = timezone.now().__str__()
184 mytimestamp = re.sub(pattern=":[^:]*$",repl="",string=mytimestamp)
185 if request.REQUEST.has_key('msg'):
186 rec.run_note += ", "+request.REQUEST['msg']+" ("+mytimestamp+")"
188 if UpdatedStatus == '1':
189 rec.run_note = "Started ("+mytimestamp+")"
192 output = "Hello "+settings.ALLOWED_IPS[ClIP]+". Updated to:'"+DataRun.RUN_STATUS_CHOICES[int(UpdatedStatus)][1].__str__()+"'"
193 except ObjectDoesNotExist:
194 output = "entry not found: "+fcid+", "+runfolder
197 #Notify researcher by email
199 #send_mail('Exp Tracker', 'Data Run Status '+output, 'rrauch@stanford.edu', ['rrrami@gmail.com'], fail_silently=False)
200 #mail_admins("test subject", "testing , testing", fail_silently=False)
201 # gives error: (49, "Can't assign requested address")
202 return HttpResponse(output)
204 def generateConfile(request,fcid):
206 #ClIP = request.META['REMOTE_ADDR']
207 #if (settings.ALLOWED_IPS.has_key(ClIP)): granted = True
209 #if not granted: return HttpResponse("access denied.")
211 config = ['READ_LENGTH 25']
212 config += ['ANALYSIS eland']
213 config += ['GENOME_FILE all_chr.fa']
214 config += ['ELAND_MULTIPLE_INSTANCES 8']
215 genome_dir = 'GENOME_DIR /Volumes/Genomes/'
216 eland_genome = 'ELAND_GENOME /Volumes/Genomes/'
219 fc = FlowCell.objects.get(flowcell_id=fcid)
220 for lane in fc.lane_set.all():
221 config += [ str(lane.lane_number) +":" + \
222 genome_dir + lane.library.library_species.scientific_name ]
223 config += [ str(lane.lane_number) +":" + \
224 eland_genome + lane.library.library_species.scientific_name ]
226 except ObjectDoesNotExist:
227 config = 'Entry not found for fcid = '+fcid
229 return os.linesep.join(config)
233 ClIP = req.META['REMOTE_ADDR']
234 if (settings.ALLOWED_IPS.has_key(ClIP)): granted = True
236 if not granted: return HttpResponse("access denied. IP: "+ClIP)
239 cnfgfile = 'Nothing found'
240 runfolder = 'unknown'
241 request = req.REQUEST
242 if request.has_key('fcid'):
243 fcid = request['fcid']
244 if request.has_key('runf'):
245 runfolder = request['runf']
247 rec = DataRun.objects.get(run_folder=runfolder) #,flowcell_id=fcid)
248 cnfgfile = rec.config_params
249 #match_str = re.compile(r"READ_LENGTH.+$")
250 match_str = re.compile('^READ_LENGTH.+')
251 if not match_str.search(cnfgfile):
252 cnfgfile = generateConfile(request,fcid)
253 if match_str.search(cnfgfile):
254 rec = DataRun.objects.get(run_folder=runfolder) #,flowcell_id=fcid)
255 rec.config_params = cnfgfile
258 cnfgfile = 'Failed generating config params for RunFolder = '+runfolder +', Flowcell id = '+ fcid+ ' Config Text:\n'+cnfgfile
260 except ObjectDoesNotExist:
261 cnfgfile = 'Entry not found for RunFolder = '+runfolder
263 return HttpResponse(cnfgfile, content_type='text/plain')
265 def getLaneLibs(req):
267 ClIP = req.META['REMOTE_ADDR']
268 if (settings.ALLOWED_IPS.has_key(ClIP)): granted = True
270 if not granted: return HttpResponse("access denied.")
272 request = req.REQUEST
275 if request.has_key('fcid'):
276 fcid = request['fcid']
278 rec = FlowCell.objects.get(flowcell_id=fcid)
280 year = datetime.today().year.__str__()
281 year = replace(year,'20','')
282 month = datetime.today().month
283 if month < 10: month = "0"+month.__str__()
284 else: month = month.__str__()
285 day = datetime.today().day
286 if day < 10: day = "0"+day.__str__()
287 else: day = day.__str__()
288 mydate = year+month+day
289 outputfile = '<?xml version="1.0" ?>'
290 outputfile += '\n<SolexaResult Date="'+mydate+'" Flowcell="'+fcid+'" Client="'+settings.ALLOWED_IPS[ClIP]+'">'
291 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=""/>'
292 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=""/>'
293 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=""/>'
294 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=""/>'
295 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=""/>'
296 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=""/>'
297 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=""/>'
298 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=""/>'
299 outputfile += '\n</SolexaResult>'
300 except ObjectDoesNotExist:
301 outputfile = 'Flowcell entry not found for: '+fcid
302 else: outputfile = 'Missing input: flowcell id'
304 return HttpResponse(outputfile, content_type='text/plain')
306 def estimateFlowcellDuration(flowcell):
308 Attempt to estimate how long it will take to run a flowcell
311 cycles = flowcell.read_length
312 if flowcell.paired_end:
314 sequencing_time = timedelta(0, cycles * flowcell.flowcell_model.per_cycle_time)
315 analysis_time = timedelta(0, flowcell.flowcell_model.fixed_time)
316 estimate_mid = sequencing_time + analysis_time
320 def estimateFlowcellTimeRemaining(flowcell):
321 estimate_mid = estimateFlowcellDuration(flowcell)
323 # offset for how long we've been running
324 running_time = timezone.now() - flowcell.run_date
325 estimate_mid -= running_time
329 def roundToDays(estimate):
331 Given a time estimate round up and down in days
334 estimate_low = timedelta(estimate.days, 0)
335 # floor estimate_mid and add a day
336 estimate_high = timedelta(estimate.days+1, 0)
338 return (estimate_low, estimate_high)
341 def makeUserLaneMap(flowcell):
343 Given a flowcell return a mapping of users interested in
344 the libraries on those lanes.
348 for lane in flowcell.lane_set.all():
349 for affiliation in lane.library.affiliations.all():
350 for user in affiliation.users.all():
351 users.setdefault(user,[]).append(lane)
355 def getUsersForFlowcell(flowcell):
358 for lane in flowcell.lane_set.all():
359 for affiliation in lane.library.affiliations.all():
360 for user in affiliation.users.all():
365 def makeUserLibraryMap(libraries):
367 Given an interable set of libraries return a mapping or
368 users interested in those libraries.
372 for library in libraries:
373 for affiliation in library.affiliations.all():
374 for user in affiliation.users.all():
375 users.setdefault(user,[]).append(library)
379 def makeAffiliationLaneMap(flowcell):
382 for lane in flowcell.lane_set.all():
383 for affiliation in lane.library.affiliations.all():
384 affs.setdefault(affiliation,[]).append(lane)
388 def makeEmailLaneMap(flowcell):
390 Create a list of email addresses and the lanes associated with those users.
392 The email addresses can come from both the "users" table and the "affiliations" table.
395 for lane in flowcell.lane_set.all():
396 for affiliation in lane.library.affiliations.all():
397 if affiliation.email is not None and len(affiliation.email) > 0:
398 emails.setdefault(affiliation.email,set()).add(lane)
399 for user in affiliation.users.all():
400 if user.email is not None and len(user.email) > 0:
401 emails.setdefault(user.email,set()).add(lane)