def test_get_state_attrs(self): # create the column for data_1 Column.objects.create( column_name=u'data_1', table_name=u'TaxLotState', organization=self.org, is_extra_data=True, ) tlv1 = self.taxlot_view_factory.get_taxlot_view( extra_data={"data_1": "value_1"}) tlv2 = self.taxlot_view_factory.get_taxlot_view( extra_data={"data_1": "value_2"}) self.assertEqual(tlv1.state.extra_data['data_1'], 'value_1') self.assertEqual(tlv2.state.extra_data['data_1'], 'value_2') res = get_state_attrs([tlv1.state, tlv2.state]) self.assertEqual(res['custom_id_1'], { tlv2.state: None, tlv1.state: None }) self.assertEqual(res['postal_code'], { tlv2.state: tlv2.state.postal_code, tlv1.state: tlv1.state.postal_code }) self.assertTrue('data_1' not in res.keys())
def merge(self, request): """ Merge multiple tax lot records into a single new record --- parameters: - name: organization_id description: The organization_id for this user's organization required: true paramType: query - name: state_ids description: Array containing tax lot state ids to merge paramType: body """ body = request.data state_ids = body.get('state_ids', []) organization_id = int(request.query_params.get('organization_id', None)) # Check the number of state_ids to merge if len(state_ids) < 2: return JsonResponse( { 'status': 'error', 'message': 'At least two ids are necessary to merge' }, status=status.HTTP_400_BAD_REQUEST) # Make sure the state isn't already matched for state_id in state_ids: if ImportFileViewSet.has_coparent(state_id, 'properties'): return JsonResponse( { 'status': 'error', 'message': 'Source state [' + state_id + '] is already matched' }, status=status.HTTP_400_BAD_REQUEST) audit_log = TaxLotAuditLog inventory = TaxLot label = apps.get_model('seed', 'TaxLot_labels') state = TaxLotState view = TaxLotView index = 1 merged_state = None while index < len(state_ids): # state 1 is the base, state 2 is merged on top of state 1 # Use index 0 the first time through, merged_state from then on if index == 1: state1 = state.objects.get(id=state_ids[index - 1]) else: state1 = merged_state state2 = state.objects.get(id=state_ids[index]) merged_state = state.objects.create( organization_id=organization_id) merged_state = merging.merge_state(merged_state, state1, state2, merging.get_state_attrs( [state1, state2]), default=state2) state_1_audit_log = audit_log.objects.filter(state=state1).first() state_2_audit_log = audit_log.objects.filter(state=state2).first() audit_log.objects.create(organization=state1.organization, parent1=state_1_audit_log, parent2=state_2_audit_log, parent_state1=state1, parent_state2=state2, state=merged_state, name='Manual Match', description='Automatic Merge', import_filename=None, record_type=AUDIT_IMPORT) # Set the merged_state to merged merged_state.data_state = DATA_STATE_MATCHING merged_state.merge_state = MERGE_STATE_MERGED merged_state.save() state1.merge_state = MERGE_STATE_UNKNOWN state1.save() state2.merge_state = MERGE_STATE_UNKNOWN state2.save() # Delete existing views and inventory records views = view.objects.filter(state_id__in=[state1.id, state2.id]) view_ids = list(views.values_list('id', flat=True)) # Find unique notes notes = list( Note.objects.values( 'name', 'note_type', 'text', 'log_data', 'created', 'updated', 'organization_id', 'user_id').filter(taxlot_view_id__in=view_ids).distinct()) cycle_id = views.first().cycle_id label_ids = [] # Get paired view ids paired_view_ids = list( TaxLotProperty.objects.filter( taxlot_view_id__in=view_ids).order_by('property_view_id'). distinct('property_view_id').values_list('property_view_id', flat=True)) for v in views: label_ids.extend( list(v.taxlot.labels.all().values_list('id', flat=True))) v.taxlot.delete() label_ids = list(set(label_ids)) # Create new inventory record inventory_record = inventory(organization_id=organization_id) inventory_record.save() # Create new labels and view for label_id in label_ids: label(taxlot_id=inventory_record.id, statuslabel_id=label_id).save() new_view = view(cycle_id=cycle_id, state_id=merged_state.id, taxlot_id=inventory_record.id) new_view.save() # Assign notes to the new view for note in notes: note['taxlot_view'] = new_view n = Note(**note) n.save() # Correct the created and updated times to match the original note Note.objects.filter(id=n.id).update(created=note['created'], updated=note['updated']) # Delete existing pairs and re-pair all to new view # Probably already deleted by cascade TaxLotProperty.objects.filter(taxlot_view_id__in=view_ids).delete() for paired_view_id in paired_view_ids: TaxLotProperty(primary=True, cycle_id=cycle_id, property_view_id=paired_view_id, taxlot_view_id=new_view.id).save() index += 1 return {'status': 'success'}
def process(self, organization_id, cycle, property_view=None): """ Process the building file that was uploaded and create the correct models for the object :param organization_id: integer, ID of organization :param cycle: object, instance of cycle object :param property_view: Existing property view of the building file that will be updated from merging the property_view.state :return: list, [status, (PropertyState|None), (PropertyView|None), messages] """ Parser = self.BUILDING_FILE_PARSERS.get(self.file_type, None) if not Parser: acceptable_file_types = ', '.join( map(dict(self.BUILDING_FILE_TYPES).get, self.BUILDING_FILE_PARSERS.keys()) ) return False, None, None, "File format was not one of: {}".format(acceptable_file_types) parser = Parser() parser.import_file(self.file.path) parser_args = [] parser_kwargs = {} if self.file_type == self.BUILDINGSYNC: parser_args.append(BuildingSync.BRICR_STRUCT) data, errors, messages = parser.process(*parser_args, **parser_kwargs) if errors or not data: return False, None, None, messages # sub-select the data that are needed to create the PropertyState object db_columns = Column.retrieve_db_field_table_and_names_from_db_tables() create_data = {"organization_id": organization_id} extra_data = {} for k, v in data.items(): # Skip the keys that are for measures and reports and process later if k in ['measures', 'reports', 'scenarios']: continue # Check if the column exists, if not, then create one. if ('PropertyState', k) in db_columns: create_data[k] = v else: extra_data[k] = v # always create the new object, then decide if we need to merge it. # create a new property_state for the object and promote to a new property_view property_state = PropertyState.objects.create(**create_data) property_state.extra_data = extra_data property_state.save() Column.save_column_names(property_state) PropertyAuditLog.objects.create( organization_id=organization_id, state_id=property_state.id, name='Import Creation', description='Creation from Import file.', import_filename=self.file.path, record_type=AUDIT_IMPORT ) # set the property_state_id so that we can list the building files by properties self.property_state_id = property_state.id self.save() # add in the measures for m in data.get('measures', []): # Find the measure in the database try: measure = Measure.objects.get( category=m['category'], name=m['name'], organization_id=organization_id, ) except Measure.DoesNotExist: # TODO: Deal with it continue # Add the measure to the join table. # Need to determine what constitutes the unique measure for a property join, _ = PropertyMeasure.objects.get_or_create( property_state_id=self.property_state_id, measure_id=measure.pk, implementation_status=PropertyMeasure.str_to_impl_status(m['implementation_status']), application_scale=PropertyMeasure.str_to_application_scale( m.get('application_scale_of_application', PropertyMeasure.SCALE_ENTIRE_FACILITY) ), category_affected=PropertyMeasure.str_to_category_affected( m.get('system_category_affected', PropertyMeasure.CATEGORY_OTHER) ), recommended=m.get('recommended', 'false') == 'true', ) join.description = m.get('description') join.property_measure_name = m.get('property_measure_name') join.cost_mv = m.get('mv_cost') join.cost_total_first = m.get('measure_total_first_cost') join.cost_installation = m.get('measure_installation_cost') join.cost_material = m.get('measure_material_cost') join.cost_capital_replacement = m.get('measure_capital_replacement_cost') join.cost_residual_value = m.get('measure_residual_value') join.save() # add in scenarios for s in data.get('scenarios', []): # measures = models.ManyToManyField(PropertyMeasure) # {'reference_case': u'Baseline', 'annual_savings_site_energy': None, # 'measures': [], 'id': u'Baseline', 'name': u'Baseline'} scenario, _ = Scenario.objects.get_or_create( name=s.get('name'), property_state_id=self.property_state_id, ) scenario.description = s.get('description') scenario.annual_site_energy_savings = s.get('annual_site_energy_savings') scenario.annual_source_energy_savings = s.get('annual_source_energy_savings') scenario.annual_cost_savings = s.get('annual_cost_savings') scenario.summer_peak_load_reduction = s.get('summer_peak_load_reduction') scenario.winter_peak_load_reduction = s.get('winter_peak_load_reduction') scenario.hdd = s.get('hdd') scenario.hdd_base_temperature = s.get('hdd_base_temperature') scenario.cdd = s.get('cdd') scenario.cdd_base_temperature = s.get('cdd_base_temperature') # temporal_status = models.IntegerField(choices=TEMPORAL_STATUS_TYPES, # default=TEMPORAL_STATUS_CURRENT) if s.get('reference_case'): ref_case = Scenario.objects.filter( name=s.get('reference_case'), property_state_id=self.property_state_id, ) if len(ref_case) == 1: scenario.reference_case = ref_case.first() # set the list of measures for measure_name in s['measures']: # find the join measure in the database measure = None try: measure = PropertyMeasure.objects.get( property_state_id=self.property_state_id, property_measure_name=measure_name, ) except PropertyMeasure.DoesNotExist: # PropertyMeasure is not in database, skipping silently continue scenario.measures.add(measure) scenario.save() if property_view: # create a new blank state to merge the two together merged_state = PropertyState.objects.create(organization_id=organization_id) # assume the same cycle id as the former state. # should merge_state also copy/move over the relationships? merged_state = merge_state(merged_state, property_view.state, property_state, get_state_attrs([property_view.state, property_state])) # log the merge # Not a fan of the parent1/parent2 logic here, seems error prone, what this # is also in here: https://github.com/SEED-platform/seed/blob/63536e99cf5be3a9a86391c5cead6dd4ff74462b/seed/data_importer/tasks.py#L1549 PropertyAuditLog.objects.create( organization_id=organization_id, parent1=PropertyAuditLog.objects.filter(state=property_view.state).first(), parent2=PropertyAuditLog.objects.filter(state=property_state).first(), parent_state1=property_view.state, parent_state2=property_state, state=merged_state, name='System Match', description='Automatic Merge', import_filename=None, record_type=AUDIT_IMPORT ) property_view.state = merged_state property_view.save() merged_state.merge_state = MERGE_STATE_MERGED merged_state.save() # set the property_state to the new one property_state = merged_state elif not property_view: property_view = property_state.promote(cycle) else: # invalid arguments, must pass both or neither return False, None, None, "Invalid arguments passed to BuildingFile.process()" return True, property_state, property_view, messages