def test_json_for_individual(self): individual = Individual.objects.first() json = _get_json_for_individual(individual) individual_fields = { 'projectGuid', 'familyGuid', 'individualGuid', 'caseReviewStatusLastModifiedBy', 'phenotipsData', 'individualId', 'paternalId', 'maternalId', 'sex', 'affected', 'displayName', 'notes', 'phenotipsPatientId', 'phenotipsData', 'createdDate', 'lastModifiedDate', 'paternalGuid', 'maternalGuid', } self.assertSetEqual(set(json.keys()), individual_fields) individual_fields.update({ 'caseReviewStatus', 'caseReviewDiscussion', 'caseReviewStatusLastModifiedDate', 'caseReviewStatusLastModifiedBy', }) user = User.objects.filter(is_staff=True).first() json = _get_json_for_individual(individual, user) self.assertSetEqual(set(json.keys()), individual_fields)
def test_json_for_individual(self): individual = Individual.objects.first() json = _get_json_for_individual(individual) self.assertSetEqual(set(json.keys()), INDIVIDUAL_FIELDS) user = User.objects.filter(is_staff=True).first() json = _get_json_for_individual(individual, user) self.assertSetEqual(set(json.keys()), INTERNAL_INDIVIDUAL_FIELDS)
def update_individual_handler(request, individual_guid): """Updates a single field in an Individual record. Args: request (object): Django HTTP Request object. individual_guid (string): GUID of the Individual. Request: body should be a json dictionary like: { 'value': xxx } Response: json dictionary representing the updated individual like: { <individualGuid> : { individualId: xxx, sex: xxx, affected: xxx, ... } } """ individual = Individual.objects.get(guid=individual_guid) project = individual.family.project check_permissions(project, request.user, CAN_EDIT) request_json = json.loads(request.body) update_individual_from_json(individual, request_json, user=request.user, allow_unknown_keys=True) return create_json_response({ individual.guid: _get_json_for_individual(individual, request.user) })
def update_individual_field_handler(request, individual_guid, field_name): """Updates an Individual record. Args: individual_guid (string): GUID of the individual. field_name (string): Name of Individual record field to update (eg. "affected"). """ individual = Individual.objects.get(guid=individual_guid) # check permission project = individual.family.project if not request.user.is_staff and not request.user.has_perm( CAN_EDIT, project): raise PermissionDenied("%s does not have EDIT permissions for %s" % (request.user, project)) request_json = json.loads(request.body) if "value" not in request_json: raise ValueError("Request is missing 'value' key") individual_json = {field_name: request_json['value']} update_individual_from_json(individual, individual_json) return create_json_response( {individual.guid: _get_json_for_individual(individual, request.user)})
def update_individual_hpo_terms(request, individual_guid): """Updates features fields for the given Individual """ individual = Individual.objects.get(guid=individual_guid) project = individual.family.project check_project_permissions(project, request.user, can_edit=True) request_json = json.loads(request.body) for feature_key in [ 'features', 'absentFeatures', 'nonstandardFeatures', 'absentNonstandardFeatures' ]: orm_key = _to_snake_case(feature_key) value = [get_parsed_feature(feature) for feature in request_json[feature_key]] \ if request_json.get(feature_key) else None setattr(individual, orm_key, value) individual.save() return create_json_response({ individual.guid: _get_json_for_individual(individual, request.user, add_hpo_details=True) })
def update_individual_hpo_terms(request, individual_guid): """Updates features fields for the given Individual """ individual = Individual.objects.get(guid=individual_guid) project = individual.family.project check_project_permissions(project, request.user, can_edit=True) request_json = json.loads(request.body) update_json = { key: [get_parsed_feature(feature) for feature in request_json[key]] if request_json.get(key) else None for key in [ 'features', 'absentFeatures', 'nonstandardFeatures', 'absentNonstandardFeatures' ] } update_model_from_json(individual, update_json, user=request.user) return create_json_response({ individual.guid: _get_json_for_individual(individual, request.user, add_hpo_details=True) })
def test_json_for_individual(self, mock_analyst_group): individual = Individual.objects.first() json = _get_json_for_individual(individual) self.assertSetEqual(set(json.keys()), INDIVIDUAL_FIELDS_NO_FEATURES) json = _get_json_for_individual(individual, add_hpo_details=True) self.assertSetEqual(set(json.keys()), INDIVIDUAL_FIELDS) user = User.objects.get(username='******') json = _get_json_for_individual(individual, user, add_hpo_details=True) self.assertSetEqual(set(json.keys()), INDIVIDUAL_FIELDS) mock_analyst_group.__bool__.return_value = True mock_analyst_group.resolve_expression.return_value = 'analysts' json = _get_json_for_individual(individual, user, add_hpo_details=True) self.assertSetEqual(set(json.keys()), INTERNAL_INDIVIDUAL_FIELDS)
def test_json_for_individual(self): individual = Individual.objects.first() json = _get_json_for_individual(individual) individual_fields = { 'projectGuid', 'familyGuid', 'individualGuid', 'caseReviewStatusLastModifiedBy', 'phenotipsData', 'individualId', 'paternalId', 'maternalId', 'sex', 'affected', 'displayName', 'notes', 'phenotipsPatientId', 'phenotipsData', 'createdDate', 'lastModifiedDate', 'paternalGuid', 'maternalGuid', 'mmeSubmittedDate', 'mmeDeletedDate', } self.assertSetEqual(set(json.keys()), individual_fields) individual_fields.update({ 'caseReviewStatus', 'caseReviewDiscussion', 'caseReviewStatusLastModifiedDate', 'caseReviewStatusLastModifiedBy', }) user = User.objects.filter(is_staff=True).first() json = _get_json_for_individual(individual, user) self.assertSetEqual(set(json.keys()), individual_fields)
def case_review_page_data(request, project_guid): """Returns a JSON object containing information used by the case review page: :: json_response = { 'user': {..}, 'project': {..}, 'familiesByGuid': {..}, 'individualsByGuid': {..}, } Args: project_guid (string): GUID of the project being case-reviewed. """ # get all families in this project project = get_project_and_check_permissions(project_guid, request.user) json_response = { 'user': _get_json_for_user(request.user), 'project': _get_json_for_project(project, request.user), 'familiesByGuid': {}, 'individualsByGuid': {}, } for i in Individual.objects.select_related('family').filter( family__project=project): # filter out individuals that were never in case review if not i.case_review_status: continue # process family record if it hasn't been added already family = i.family if family.guid not in json_response['familiesByGuid']: json_response['familiesByGuid'][ family.guid] = _get_json_for_family(family, request.user) json_response['familiesByGuid'][ family.guid]['individualGuids'] = [] json_response['individualsByGuid'][i.guid] = _get_json_for_individual( i, request.user) json_response['familiesByGuid'][family.guid]['individualGuids'].append( i.guid) return create_json_response(json_response)
def update_individual_field_handler(request, individual_guid, field_name): """Updates a single field in an Individual record. Args: request (object): Django HTTP Request object. individual_guid (string): GUID of the Individual. field_name (string): Name of the field to update (eg. "maternalId"). Request: body should be a json dictionary like: { 'value': xxx } Response: json dictionary representing the updated individual like: { <individualGuid> : { individualId: xxx, maternalId: xxx, affected: xxx, ... } } """ individual = Individual.objects.get(guid=individual_guid) project = individual.family.project check_permissions(project, request.user, CAN_EDIT) request_json = json.loads(request.body) if "value" not in request_json: raise ValueError("Request is missing 'value' key: %s" % (request.body, )) individual_json = {field_name: request_json['value']} update_individual_from_json(individual, individual_json) return create_json_response( {individual.guid: _get_json_for_individual(individual, request.user)})
def save_individuals_table_handler(request, project_guid, upload_file_id): """Handler for 'save' requests to apply Individual tables previously uploaded through receive_individuals_table(..) Args: request (object): Django request object project_guid (string): project GUID uploadedFileId (string): a token sent to the client by receive_individuals_table(..) """ project = get_project_and_check_permissions(project_guid, request.user) serialized_file_path = _compute_serialized_file_path(upload_file_id) with gzip.open(serialized_file_path) as f: json_records = json.load(f) updated_families, updated_individuals = add_or_update_individuals_and_families( project, individual_records=json_records) os.remove(serialized_file_path) # edit individuals individuals_by_guid = { individual.guid: _get_json_for_individual(individual, request.user) for individual in updated_individuals } families_by_guid = { family.guid: _get_json_for_family(family, request.user, add_individual_guids_field=True) for family in updated_families } # families whose list of individuals may have changed updated_families_and_individuals_by_guid = { 'individualsByGuid': individuals_by_guid, 'familiesByGuid': families_by_guid, } return create_json_response(updated_families_and_individuals_by_guid)
def update_individual_field_handler(request, individual_guid, field_name): """Updates an Individual record. Args: individual_guid (string): GUID of the individual. field_name (string): Name of Individual record field to update (eg. "affected"). """ individual = Individual.objects.get(guid=individual_guid) project = individual.family.project check_permissions(project, request.user, CAN_EDIT) request_json = json.loads(request.body) if "value" not in request_json: raise ValueError("Request is missing 'value' key: %s" % (request.body, )) individual_json = {field_name: request_json['value']} update_individual_from_json(individual, individual_json) return create_json_response( {individual.guid: _get_json_for_individual(individual, request.user)})
def edit_individuals_handler(request, project_guid): """Modify one or more Individual records. Args: request (object): Django HTTP Request object. project_guid (string): GUID of project that contains these individuals. Request: body should be a json dictionary that contains a 'individuals' list that includes the individuals to update, represented by dictionaries of their guid and fields to update - for example: { 'individuals': [ { 'individualGuid': <individualGuid1>, 'paternalId': <paternalId>, 'affected': 'A' }, { 'individualGuid': <individualGuid1>, 'sex': 'U' }, ... [ } Response: json dictionary representing the updated individual(s) like: { <individualGuid1> : { individualId: xxx, sex: xxx, affected: xxx, ...}, <individualGuid2> : { individualId: xxx, sex: xxx, affected: xxx, ...}, ... } """ project = get_project_and_check_permissions(project_guid, request.user, CAN_EDIT) request_json = json.loads(request.body) modified_individuals_list = request_json.get('individuals') if modified_individuals_list is None: return create_json_response( {}, status=400, reason="'individuals' not specified") update_individuals = {ind['individualGuid']: ind for ind in modified_individuals_list} update_individual_models = {ind.guid: ind for ind in Individual.objects.filter(guid__in=update_individuals.keys())} for modified_ind in modified_individuals_list: model = update_individual_models[modified_ind['individualGuid']] if modified_ind[JsonConstants.INDIVIDUAL_ID_COLUMN] != model.individual_id: modified_ind[JsonConstants.PREVIOUS_INDIVIDUAL_ID_COLUMN] = model.individual_id modified_family_ids = {ind.get('familyId') or ind['family']['familyId'] for ind in modified_individuals_list} modified_family_ids.update({ind.family.family_id for ind in update_individual_models.values()}) related_individuals = Individual.objects.filter( family__family_id__in=modified_family_ids, family__project=project).exclude(guid__in=update_individuals.keys()) related_individuals_json = _get_json_for_individuals(related_individuals, project_guid=project_guid, family_fields=['family_id']) individuals_list = modified_individuals_list + related_individuals_json # TODO more validation? errors, warnings = validate_fam_file_records(individuals_list, fail_on_warnings=True) if errors: return create_json_response({'errors': errors, 'warnings': warnings}, status=400, reason='Invalid updates') try: updated_families, updated_individuals = add_or_update_individuals_and_families( project, modified_individuals_list, user=request.user ) except Exception as e: return create_json_response({'errors': [e.message]}, status=400, reason='Invalid updates') individuals_by_guid = { individual.guid: _get_json_for_individual(individual, request.user) for individual in updated_individuals } families_by_guid = { family.guid: _get_json_for_family(family, request.user, add_individual_guids_field=True) for family in updated_families } return create_json_response({ 'individualsByGuid': individuals_by_guid, 'familiesByGuid': families_by_guid, })
def update_pedigree_image(family): """Uses HaploPainter to (re)generate the pedigree image for the given family. Args: family (object): seqr Family model. """ family_id = family.family_id individuals = Individual.objects.filter(family=family) if len(individuals) < 2: update_seqr_model(family, pedigree_image=None) return # convert individuals to json individual_records = {} for i in individuals: individual_records[i.individual_id] = _get_json_for_individual(i) individual_records[i.individual_id]['familyId'] = i.family.family_id # compute a map of parent ids to list of children parent_ids_to_children_map = collections.defaultdict(list) for individual_id, individual_json in individual_records.items(): if not individual_json['paternalId'] and not individual_json[ 'maternalId']: continue key = (individual_json['paternalId'], individual_json['maternalId']) parent_ids_to_children_map[key].append(individual_json) # generate placeholder individuals as needed, since HaploPainter1.043.pl doesn't support families with only 1 parent for ((paternal_id, maternal_id), children) in parent_ids_to_children_map.items(): for parent_id_key, parent_id, sex in [('paternalId', paternal_id, 'M'), ('maternalId', maternal_id, 'F') ]: if not parent_id or parent_id not in individual_records: placeholder_parent_id = 'placeholder_%s' % _random_string(10) placeholder_parent_json = { 'familyId': family_id, 'individualId': placeholder_parent_id, # fake indiv id 'paternalId': '', 'maternalId': '', 'sex': sex, 'affected': 'INVISIBLE', # use a special value to tell HaploPainter to draw this individual as '?' } for child_json in children: child_json[parent_id_key] = placeholder_parent_id individual_records[ placeholder_parent_id] = placeholder_parent_json # convert to FAM file values SEX_TO_FAM_FILE_VALUE = {"M": "1", "F": "2", "U": "0"} AFFECTED_STATUS_TO_FAM_FILE_VALUE = { "A": "2", "N": "1", "U": "0", "INVISIBLE": "9" } # HaploPainter1.043.pl has been modified to hide individuals with affected-status='9' for individual_json in individual_records.values(): if not individual_json['paternalId']: individual_json['paternalId'] = '0' if not individual_json['maternalId']: individual_json['maternalId'] = '0' individual_json['sex'] = SEX_TO_FAM_FILE_VALUE[individual_json['sex']] individual_json['affected'] = AFFECTED_STATUS_TO_FAM_FILE_VALUE[ individual_json['affected']] # run HaploPainter to generate the pedigree image png_file_path = os.path.join(tempfile.gettempdir(), "pedigree_image_%s.png" % _random_string(10)) with tempfile.NamedTemporaryFile('w', suffix=".fam", delete=True) as fam_file: # columns: family, individual id, paternal id, maternal id, sex, affected for i in individual_records.values(): row = [ i[key] for key in [ 'familyId', 'individualId', 'paternalId', 'maternalId', 'sex', 'affected' ] ] fam_file.write("\t".join(row).encode('UTF-8')) fam_file.write("\n") fam_file.flush() fam_file_path = fam_file.name haplopainter_command = "/usr/bin/perl " + os.path.join( BASE_DIR, "xbrowse_server/base/management/commands/HaploPainter1.043.pl") haplopainter_command += " -b -outformat png -pedfile %(fam_file_path)s -family %(family_id)s -outfile %(png_file_path)s" % locals( ) os.system(haplopainter_command) if not os.path.isfile(png_file_path): logger.error("Failed to generated pedigree image for family: %s" % family_id) update_seqr_model(family, pedigree_image=None) return _save_pedigree_image_file(family, png_file_path) os.remove(png_file_path)
def save_case_review_status(request): """Updates the `case_review_status` of one or more individuals. HTTP POST Request body - should contain json: { form: { <individualGuid1> : <case review status>, <individualGuid2> : <case review status>, .. } } Response body - will be json with the following structure, representing the created project: { <individualGuid1> : { ... <individual key-value pairs> ... }, } """ request_json = json.loads(request.body) if "form" not in request_json: raise ValueError("Request is missing 'value' key: %s" % (request.body, )) response_json = {} for individual_guid, case_review_status_change in request_json[ 'form'].items(): i = Individual.objects.get(guid=individual_guid) # keep new seqr.Project model in sync with existing xbrowse_server.base.models - TODO remove this code after transition to new schema is finished base_project = BaseProject.objects.filter( project_id=i.family.project.deprecated_project_id) if base_project: base_project = base_project[0] base_i = BaseIndividual.objects.get(family__project=base_project, indiv_id=i.individual_id) value = case_review_status_change.get('value') action = case_review_status_change.get('action') if action == 'UPDATE_CASE_REVIEW_STATUS': if i.case_review_status == value: continue # keep new seqr.Project model in sync with existing xbrowse_server.base.models - TODO remove this code after transition to new schema is finished i.case_review_status = value base_i.case_review_status = i.case_review_status elif action == 'ADD_ACCEPTED_FOR': if i.case_review_status_accepted_for and ( value in i.case_review_status_accepted_for): continue # keep new seqr.Project model in sync with existing xbrowse_server.base.models - TODO remove this code after transition to new schema is finished i.case_review_status_accepted_for = "".join( sorted(set((i.case_review_status_accepted_for or "") + value))) base_i.case_review_status_accepted_for = i.case_review_status_accepted_for elif action == 'REMOVE_ACCEPTED_FOR': if not i.case_review_status_accepted_for or ( value not in i.case_review_status_accepted_for): continue i.case_review_status_accepted_for = i.case_review_status_accepted_for.replace( value, "") base_i.case_review_status_accepted_for = i.case_review_status_accepted_for else: raise ValueError("Unexpected action param: {0}".format( case_review_status_change.get('action'))) print("Saving individual: %s %s %s" % (i.individual_id, i.case_review_status, i.case_review_status_accepted_for)) i.case_review_status_last_modified_by = request.user i.case_review_status_last_modified_date = timezone.now() i.save() base_i.save() response_json[i.guid] = _get_json_for_individual(i, request.user) return create_json_response(response_json)
def edit_individuals_handler(request, project_guid): """Modify one or more Individual records. Args: request (object): Django HTTP Request object. project_guid (string): GUID of project that contains these individuals. Request: body should be a json dictionary that contains a 'modifiedIndividuals' dictionary that includes the individuals to update, mapped to the specific fields that should be updated for each individual - for example: { 'form': { 'modifiedIndividuals': { <individualGuid1>: { 'paternalId': <paternalId>, 'affected': 'A' }, <individualGuid2>: { 'sex': 'U' }, ... } } } Response: json dictionary representing the updated individual(s) like: { <individualGuid1> : { individualId: xxx, maternalId: xxx, paternalId: xxx, ...}, <individualGuid2> : { individualId: xxx, maternalId: xxx, paternalId: xxx, ...}, ... } """ project = get_project_and_check_permissions(project_guid, request.user, CAN_EDIT) request_json = json.loads(request.body) if 'form' not in request_json: return create_json_response( {}, status=400, reason="Invalid request: 'form' key not specified") form_data = request_json['form'] modified_individuals_by_guid = form_data.get('modifiedIndividuals') if modified_individuals_by_guid is None: return create_json_response( {}, status=400, reason="'modifiedIndividuals' not specified") # edit individuals modified_individuals_list = list(modified_individuals_by_guid.values()) # TODO more validation errors, warnings = validate_fam_file_records(modified_individuals_list) if errors: return create_json_response({'errors': errors, 'warnings': warnings}) updated_families, updated_individuals = add_or_update_individuals_and_families( project, modified_individuals_list) individuals_by_guid = { individual.guid: _get_json_for_individual(individual, request.user) for individual in updated_individuals } families_by_guid = { family.guid: _get_json_for_family(family, request.user, add_individual_guids_field=True) for family in updated_families } # families whose list of individuals may have changed updated_individuals_by_guid = { 'individualsByGuid': individuals_by_guid, 'familiesByGuid': families_by_guid, } return create_json_response(updated_individuals_by_guid)