diff --git a/xbrowse_server/api/urls.py b/xbrowse_server/api/urls.py index f1448a11f0..f4c40ae4f6 100644 --- a/xbrowse_server/api/urls.py +++ b/xbrowse_server/api/urls.py @@ -46,4 +46,5 @@ #phenotips related url(r'^phenotips/proxy/edit/(?P[\w|-]+)$', 'xbrowse_server.phenotips.views.fetch_phenotips_edit_page', name='fetch_phenotips_edit_page'), + url(r'^phenotips/proxy/view/(?P[\w|-]+)$', 'xbrowse_server.phenotips.views.fetch_phenotips_pdf_page', name='fetch_phenotips_pdf_page'), ) diff --git a/xbrowse_server/base/views/project_views.py b/xbrowse_server/base/views/project_views.py index 9c8745b913..1d7c80535d 100644 --- a/xbrowse_server/base/views/project_views.py +++ b/xbrowse_server/base/views/project_views.py @@ -36,9 +36,17 @@ from xbrowse_server.mall import get_reference from xbrowse_server import mall from xbrowse_server.gene_lists.views import download_response as gene_list_download_response +from xbrowse_server.phenotips.reporting_utilities import get_phenotype_entry_metrics_for_project +from xbrowse_server.phenotips.reporting_utilities import categorize_phenotype_counts +from xbrowse_server.phenotips.reporting_utilities import aggregate_phenotype_counts_into_bins +from xbrowse_server.decorators import log_request +import logging + +logger = logging.getLogger(__name__) @login_required +@log_request('project_views') def project_home(request, project_id): project = get_object_or_404(Project, project_id=project_id) @@ -58,7 +66,25 @@ def project_home(request, project_id): else: raise Exception("Authx - how did we get here?!?") + phenotips_supported=False + if project_id in settings.PHENOTIPS_SUPPORTED_PROJECTS: + phenotips_supported=True + + indiv_phenotype_counts=[] + binned_counts={} + categorized_phenotype_counts={} + if phenotips_supported: + try: + indiv_phenotype_counts= get_phenotype_entry_metrics_for_project(project_id) + binned_counts=aggregate_phenotype_counts_into_bins(indiv_phenotype_counts) + categorized_phenotype_counts=categorize_phenotype_counts(binned_counts) + except Exception as e: + print 'error looking for project information in PhenoTips:logging & moving,there might not be any data' + logger.error('project_views:'+str(e)) + return render(request, 'project.html', { + 'categorized_phenotype_counts':categorized_phenotype_counts, + 'phenotips_supported':phenotips_supported, 'project': project, 'auth_level': auth_level, 'can_edit': project.can_edit(request.user), diff --git a/xbrowse_server/phenotips/reporting_utilities.py b/xbrowse_server/phenotips/reporting_utilities.py new file mode 100644 index 0000000000..465dd9b5ee --- /dev/null +++ b/xbrowse_server/phenotips/reporting_utilities.py @@ -0,0 +1,193 @@ +from xbrowse_server.base.models import Project +from xbrowse_server.phenotips.utilities import get_uname_pwd_for_project +import os +from django.conf import settings +import requests +from requests.auth import HTTPBasicAuth + +def get_phenotype_entry_metrics_for_project(project_id): + ''' + Processes the given project + Inputs: + project: a project ID + ''' + try: + project = Project.objects.get(project_id=project_id) + individuals=[] + for individual in project.get_individuals(): + individuals.append(individual.indiv_id) + return get_phenotype_entry_details_for_individuals(individuals,project_id) + except Exception as e: + print '\nsorry, we encountered an error finding project:',e,'\n' + raise + +def aggregate_phenotype_counts_into_bins(phenotype_counts): + ''' + Given a list of individual phenotype counts, aggregates these into + bins of counts + Input: + A list of dicts that have indiv ID and how many phenotypes entered + [{}, ....] + Ex: + [{'eid': u'NA19675', 'num_phenotypes_entered': 1},....] + + Output: + A dict that resembles (the "count" here is derived from "num_phenotypes_entered": + {count: [indiv1, indiv2, indiv3 ..indivs with this many phenotypes entered], + count2: [indivX,...]} + ''' + aggregated={} + for count in phenotype_counts: + if aggregated.has_key(count['num_phenotypes_entered']): + aggregated[count['num_phenotypes_entered']].append(count['eid']) + else: + aggregated[count['num_phenotypes_entered']] = [count['eid']] + return aggregated + + + +def get_phenotype_entry_details_for_individuals(individuals,project_id): + ''' + Process this list of individuals + + Inputs: + individuals: a list of individuals + ''' + all_patients=[] + try: + for individual in individuals: + phenotype_count_for_indiv=phenotype_entry_metric_for_individual(individual,project_id) + all_patients.append({'eid':individual,'num_phenotypes_entered':phenotype_count_for_indiv}) + return all_patients + except Exception as e: + raise + + + + +def print_details_to_stdout(proj_dets,summarize): + ''' + Print details to STDOUT + + Inputs: + proj_dets: a project details structure + summarize: True/False whether to summarize view + ''' + category_names=self.get_phenotype_count_categorie_names() + if summarize: + print '{:20s} {:20s} {:20s} {:20s} {:20s} {:20s}'.format('Project',*category_names) + try: + for proj_id,dets in proj_dets.iteritems(): + phenotype_counts={} + for patient_det in dets: + if not summarize: + print patient_det['eid'],patient_det['num_phenotypes_entered'] + if summarize: + if phenotype_counts.has_key(patient_det['num_phenotypes_entered']): + phenotype_counts[patient_det['num_phenotypes_entered']].append(patient_det['eid']) + else: + phenotype_counts[patient_det['num_phenotypes_entered']]=[patient_det['eid']] + if summarize: + data=categorize_phenotype_counts(phenotype_counts) + print '{:20s}'.format(proj_id), + for category_name in category_names: + print '{:20s}'.format(str(len(data[category_name]))), + print + except Exception as e: + raise + + +def categorize_phenotype_counts(phenotype_counts): + ''' + Bin counts in categories for easy reporting in columns + + Categories are: + 0 + 0-10 + 10-20 + >30 + + Notes: + If you add any new categories, remember to update method + get_phenotype_count_categorie_names. + + Inputs: + A dict of number of patients to number of phenotypes + entered for each patient + + Outputs: + -A dict with keys of above categories and values being count + of each. + -A tuple with category names + ''' + category_names=get_phenotype_count_categorie_names() + data={} + for c in category_names: + data[c]=[] + for phenotype_count,patients in phenotype_counts.iteritems(): + if phenotype_count ==0: + data['0'].extend(patients) + if phenotype_count>0 and phenotype_count<11: + data['1to10'].extend(patients) + if phenotype_count>=11 and phenotype_count<=20: + data['11to20'].extend(patients) + if phenotype_count>=21 and phenotype_count<=30: + data['21to30'].extend(patients) + if phenotype_count>=31: + data['larger_than_31'].extend(patients) + return data + + +def get_phenotype_count_categorie_names(): + ''' + Return a tuple of category names used + categorize_phenotype_counts. + Notes: + Any updates to this function must coincide with method + + ''' + return ('0','1to10','11to20','21to30','larger_than_31') + + + + + +def get_phenotypes_entered_for_individual(indiv_id,project_id): + ''' + Get phenotype data enterred for this individual. + + Inputs: + indiv_id: an individual ID (ex: PIE-OGI855-001726) + ''' + try: + uname,pwd = get_uname_pwd_for_project(project_id,read_only=True) + url = os.path.join(settings.PHENOPTIPS_HOST_NAME,'rest/patients/eid/' + indiv_id) + response = requests.get(url, auth=HTTPBasicAuth(uname,pwd)) + return response.json() + except Exception as e: + print 'patient phenotype export error:',e + raise + + + + + +def phenotype_entry_metric_for_individual(indiv_id, project_id): + ''' + Determine a metric that describes the level of phenotype entry for this + individual. + + Notes: + 1. Phenotype terms appear in both features (where HPO terms exist) + and in nonstandard_features where phenotypes were defined in + regular text where HPO might not have existed. + + Inputs: + indiv_id: an individual ID (ex: PIE-OGI855-001726) + ''' + entered_phenotypes=get_phenotypes_entered_for_individual(indiv_id,project_id) + count=0 + for k,v in entered_phenotypes.iteritems(): + if k=='features' or k=='nonstandard_features': + count = count + len(v) + return count \ No newline at end of file diff --git a/xbrowse_server/phenotips/views.py b/xbrowse_server/phenotips/views.py index 6eb27cd726..cf2dcdbb7f 100644 --- a/xbrowse_server/phenotips/views.py +++ b/xbrowse_server/phenotips/views.py @@ -109,6 +109,44 @@ def fetch_phenotips_edit_page(request,eid): +@log_request('phenotips_proxy_pdf_page') +@login_required +@csrf_exempt +def fetch_phenotips_pdf_page(request,eid): + ''' + A proxy for phenotips view and edit patient pages + Notes: + - Exempting csrf here since phenotips doesn't have this support + - Each call to this endpoint is atomic, no session information is kept + between calls. Each call to this is from within a new Frame, hence no + notion of session is kept. Each is a new login into PhenoTips. + ''' + try: + current_user = request.user + project_id=request.GET['project'] + uname,pwd = get_uname_pwd_for_project(project_id,read_only=True) + ext_id=convert_internal_id_to_external_id(eid,uname,pwd) + auth_level=get_auth_level(project_id,request.user) + if auth_level == 'unauthorized': + return HttpResponse('unauthorized') + url= settings.PHENOPTIPS_HOST_NAME + '/bin/export/data/' + ext_id + '?format=pdf&pdfcover=0&pdftoc=0&pdftemplate=PhenoTips.PatientSheetCode' + response,curr_session = do_authenticated_call_to_phenotips(url,uname,pwd) + http_response=HttpResponse(response.content) + for header in response.headers.keys(): + if header != 'connection' and header != 'transfer-encoding': #these hop-by-hop headers are not allowed by Django + http_response[header]=response.headers[header] + return http_response + except Exception as e: + print e + raise Http404 + + + + + + + + @log_request('proxy_get') @login_required @csrf_exempt diff --git a/xbrowse_server/templates/family/family_home.html b/xbrowse_server/templates/family/family_home.html index 5928ab66a8..9b1dbf673a 100644 --- a/xbrowse_server/templates/family/family_home.html +++ b/xbrowse_server/templates/family/family_home.html @@ -131,7 +131,7 @@

Individuals

Mother Father {% if phenotips_supported %} - Phenotips + PhenoTips {% endif %} @@ -145,7 +145,12 @@

Individuals

{% if indiv.maternal_id %}{{ indiv.maternal_id }}{% else %}-{% endif %} {% if indiv.paternal_id %}{{ indiv.paternal_id }}{% else %}-{% endif %} {% if phenotips_supported %} - + + {% if user_is_admin %} + + {% endif %} + + {% endif %} {% endfor %} @@ -161,7 +166,7 @@

Individuals

+ + @@ -202,6 +216,11 @@ $('#phenotipsEditPatientFrame').attr('src', uri); $('#phenotipsModal').modal('show'); } + function showViewPageForThisPatient(patientId,project) { + var uri = '/api/phenotips/proxy/view/' + patientId + '?' + 'project=' + project; + $('#phenotipsEditPatientFrame').attr('src', uri); + $('#phenotipsModal').modal('show'); + }