def csv(self, request): """ Download a csv of the TaxLot and Properties .. code-block:: { "ids": [1,2,3], "columns": ["tax_jurisdiction_tax_lot_id", "address_line_1", "property_view_id"] } --- parameter_strategy: replace parameters: - name: cycle description: cycle required: true paramType: query - name: inventory_type description: properties or taxlots (as defined by the inventory list page) required: true paramType: query - name: ids description: list of property ids to export (not property views) required: true paramType: body - name: columns description: list of columns to export required: true paramType: body - name: filename description: name of the file to create required: false paramType: body - name: profile_id description: Either an id of a list settings profile, or undefined paramType: body """ cycle_pk = request.query_params.get('cycle_id', None) if not cycle_pk: return JsonResponse({ 'status': 'error', 'message': 'Must pass in cycle_id as query parameter' }) org_id = request.query_params['organization_id'] if 'profile_id' not in request.data: profile_id = None else: if request.data['profile_id'] == 'None': profile_id = None else: profile_id = request.data['profile_id'] # get the class to operate on and the relationships view_klass_str = request.query_params.get('inventory_type', 'properties') view_klass = INVENTORY_MODELS[view_klass_str] # Set the first column to be the ID column_name_mappings = OrderedDict([('id', 'ID')]) column_ids, add_column_name_mappings, columns_from_database = ColumnListSetting.return_columns( org_id, profile_id, view_klass_str) column_name_mappings.update(add_column_name_mappings) select_related = ['state', 'cycle'] ids = request.data.get('ids', []) filter_str = {'cycle': cycle_pk} if hasattr(view_klass, 'property'): select_related.append('property') filter_str = {'property__organization_id': org_id} if ids: filter_str['property__id__in'] = ids # always export the labels column_name_mappings['property_labels'] = 'Property Labels' elif hasattr(view_klass, 'taxlot'): select_related.append('taxlot') filter_str = {'taxlot__organization_id': org_id} if ids: filter_str['taxlot__id__in'] = ids # always export the labels column_name_mappings['taxlot_labels'] = 'Tax Lot Labels' model_views = view_klass.objects.select_related( *select_related).filter(**filter_str).order_by('id') filename = request.data.get('filename', "ExportedData.csv") response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="{}"'.format( filename) writer = csv.writer(response) # get the data in a dict which includes the related data data = TaxLotProperty.get_related(model_views, column_ids, columns_from_database) # force the data into the same order as the IDs if ids: order_dict = {obj_id: index for index, obj_id in enumerate(ids)} data.sort(key=lambda x: order_dict[x['id']] ) # x is the property/taxlot object # check the first item in the header and make sure that it isn't ID (it can be id, or iD). # excel doesn't like the first item to be ID in a CSV header = list(column_name_mappings.values()) if header[0] == 'ID': header[0] = 'id' writer.writerow(header) # iterate over the results to preserve column order and write row. for datum in data: row = [] for column in column_name_mappings: row_result = datum.get(column, None) # Try grabbing the value out of the related field if not found yet. if row_result is None and datum.get('related'): row_result = datum['related'][0].get(column, None) # Convert quantities (this is typically handled in the JSON Encoder, but that isn't here). if isinstance(row_result, ureg.Quantity): row_result = row_result.magnitude elif isinstance(row_result, datetime.datetime): row_result = row_result.strftime("%Y-%m-%d %H:%M:%S") elif isinstance(row_result, datetime.date): row_result = row_result.strftime("%Y-%m-%d") row.append(row_result) writer.writerow(row) return response
def _get_filtered_results(self, request, columns): page = request.query_params.get('page', 1) per_page = request.query_params.get('per_page', 1) org_id = request.query_params.get('organization_id', None) cycle_id = request.query_params.get('cycle') if not org_id: return JsonResponse( { 'status': 'error', 'message': 'Need to pass organization_id as query parameter' }, status=status.HTTP_400_BAD_REQUEST) if cycle_id: cycle = Cycle.objects.get(organization_id=org_id, pk=cycle_id) else: cycle = Cycle.objects.filter( organization_id=org_id).order_by('name') if cycle: cycle = cycle.first() else: return JsonResponse({ 'status': 'error', 'message': 'Could not locate cycle', 'pagination': { 'total': 0 }, 'cycle_id': None, 'results': [] }) taxlot_views_list = TaxLotView.objects.select_related('taxlot', 'state', 'cycle') \ .filter(taxlot__organization_id=request.query_params['organization_id'], cycle=cycle) \ .order_by('id') paginator = Paginator(taxlot_views_list, per_page) try: taxlot_views = paginator.page(page) page = int(page) except PageNotAnInteger: taxlot_views = paginator.page(1) page = 1 except EmptyPage: taxlot_views = paginator.page(paginator.num_pages) page = paginator.num_pages response = { 'pagination': { 'page': page, 'start': paginator.page(page).start_index(), 'end': paginator.page(page).end_index(), 'num_pages': paginator.num_pages, 'has_next': paginator.page(page).has_next(), 'has_previous': paginator.page(page).has_previous(), 'total': paginator.count }, 'cycle_id': cycle.id, 'results': TaxLotProperty.get_related(taxlot_views, columns) } return JsonResponse(response, encoder=PintJSONEncoder)
def test_merged_indicators_provided_on_filter_endpoint(self): _import_record, import_file_1 = self.create_import_file( self.user, self.org, self.cycle) base_details = { 'address_line_1': '123 Match Street', 'import_file_id': import_file_1.id, 'data_state': DATA_STATE_MAPPING, 'no_default_data': False, } self.taxlot_state_factory.get_taxlot_state(**base_details) # set import_file_1 mapping done so that record is "created for users to view". import_file_1.mapping_done = True import_file_1.save() geocode_and_match_buildings_task(import_file_1.id) _import_record_2, import_file_2 = self.create_import_file( self.user, self.org, self.cycle) url = reverse( 'api:v3:taxlots-filter' ) + '?cycle_id={}&organization_id={}&page=1&per_page=999999999'.format( self.cycle.pk, self.org.pk) response = self.client.post(url, content_type='application/json') data = json.loads(response.content) self.assertFalse(data['results'][0]['merged_indicator']) # make sure merged_indicator is True when merge occurs base_details['city'] = 'Denver' base_details['import_file_id'] = import_file_2.id self.taxlot_state_factory.get_taxlot_state(**base_details) # set import_file_2 mapping done so that match merging can occur. import_file_2.mapping_done = True import_file_2.save() geocode_and_match_buildings_task(import_file_2.id) url = reverse( 'api:v3:taxlots-filter' ) + '?cycle_id={}&organization_id={}&page=1&per_page=999999999'.format( self.cycle.pk, self.org.pk) response = self.client.post(url, content_type='application/json') data = json.loads(response.content) self.assertTrue(data['results'][0]['merged_indicator']) # Create pairings and check if paired object has indicator as well property_factory = FakePropertyFactory(organization=self.org) property_state_factory = FakePropertyStateFactory( organization=self.org) property = property_factory.get_property() property_state = property_state_factory.get_property_state() property_view = PropertyView.objects.create(property=property, cycle=self.cycle, state=property_state) # attach pairing to one and only taxlot_view TaxLotProperty(primary=True, cycle_id=self.cycle.id, property_view_id=property_view.id, taxlot_view_id=TaxLotView.objects.get().id).save() url = reverse( 'api:v3:taxlots-filter' ) + '?cycle_id={}&organization_id={}&page=1&per_page=999999999'.format( self.cycle.pk, self.org.pk) response = self.client.post(url, content_type='application/json') data = json.loads(response.content) related = data['results'][0]['related'][0] self.assertTrue('merged_indicator' in related) self.assertFalse(related['merged_indicator'])
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, changes = 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 unmerge(self, request, pk=None): """ Unmerge a taxlot view into two taxlot views --- parameters: - name: organization_id description: The organization_id for this user's organization required: true paramType: query """ try: old_view = TaxLotView.objects.select_related( 'taxlot', 'cycle', 'state').get(id=pk, taxlot__organization_id=self. request.GET['organization_id']) except TaxLotView.DoesNotExist: return { 'status': 'error', 'message': 'taxlot view with id {} does not exist'.format(pk) } notes = old_view.notes.all() for note in notes: note.taxlot_view = None merged_state = old_view.state if merged_state.data_state != DATA_STATE_MATCHING or merged_state.merge_state != MERGE_STATE_MERGED: return { 'status': 'error', 'message': 'taxlot view with id {} is not a merged taxlot view'.format(pk) } log = TaxLotAuditLog.objects.select_related( 'parent_state1', 'parent_state2').filter( state=merged_state).order_by('-id').first() if log.parent_state1 is None or log.parent_state2 is None: return { 'status': 'error', 'message': 'taxlot view with id {} must have two parent states'.format(pk) } label = apps.get_model('seed', 'TaxLot_labels') state1 = log.parent_state1 state2 = log.parent_state2 cycle_id = old_view.cycle_id # Clone the taxlot record, then the labels old_taxlot = old_view.taxlot label_ids = list(old_taxlot.labels.all().values_list('id', flat=True)) new_taxlot = old_taxlot new_taxlot.id = None new_taxlot.save() for label_id in label_ids: label(taxlot_id=new_taxlot.id, statuslabel_id=label_id).save() # Create the views new_view1 = TaxLotView(cycle_id=cycle_id, taxlot_id=new_taxlot.id, state=state1) new_view2 = TaxLotView(cycle_id=cycle_id, taxlot_id=old_view.taxlot_id, state=state2) # Mark the merged state as deleted merged_state.merge_state = MERGE_STATE_DELETE merged_state.save() # Change the merge_state of the individual states if log.parent1.name in ['Import Creation', 'Manual Edit' ] and log.parent1.import_filename is not None: # State belongs to a new record state1.merge_state = MERGE_STATE_NEW else: state1.merge_state = MERGE_STATE_MERGED if log.parent2.name in ['Import Creation', 'Manual Edit' ] and log.parent2.import_filename is not None: # State belongs to a new record state2.merge_state = MERGE_STATE_NEW else: state2.merge_state = MERGE_STATE_MERGED state1.save() state2.save() # Delete the audit log entry for the merge log.delete() # Duplicate pairing paired_view_ids = list( TaxLotProperty.objects.filter(taxlot_view_id=old_view.id).order_by( 'property_view_id').values_list('property_view_id', flat=True)) old_view.delete() new_view1.save() new_view2.save() # Duplicate notes to the new views for note in notes: created = note.created updated = note.updated note.id = None note.taxlot_view = new_view1 note.save() ids = [note.id] note.id = None note.taxlot_view = new_view2 note.save() ids.append(note.id) # Correct the created and updated times to match the original note Note.objects.filter(id__in=ids).update(created=created, updated=updated) for paired_view_id in paired_view_ids: TaxLotProperty(primary=True, cycle_id=cycle_id, taxlot_view_id=new_view1.id, property_view_id=paired_view_id).save() TaxLotProperty(primary=True, cycle_id=cycle_id, taxlot_view_id=new_view2.id, property_view_id=paired_view_id).save() return {'status': 'success', 'view_id': new_view1.id}
def export(self, request): """ Download a csv of the TaxLot and Properties .. code-block:: { "ids": [1,2,3], "columns": ["tax_jurisdiction_tax_lot_id", "address_line_1", "property_view_id"] } --- parameter_strategy: replace parameters: - name: cycle description: cycle required: true paramType: query - name: inventory_type description: properties or taxlots (as defined by the inventory list page) required: true paramType: query - name: ids description: list of property/taxlot ids to export (not property/taxlot views) required: true paramType: body - name: columns description: list of columns to export required: true paramType: body - name: filename description: name of the file to create required: false paramType: body - name: profile_id description: Either an id of a list settings profile, or undefined paramType: body """ cycle_pk = request.query_params.get('cycle_id', None) if not cycle_pk: return JsonResponse( {'status': 'error', 'message': 'Must pass in cycle_id as query parameter'}) org_id = request.query_params['organization_id'] if 'profile_id' not in request.data: profile_id = None else: if request.data['profile_id'] == 'None' or request.data['profile_id'] == '': profile_id = None else: profile_id = request.data['profile_id'] # get the class to operate on and the relationships view_klass_str = request.query_params.get('inventory_type', 'properties') view_klass = INVENTORY_MODELS[view_klass_str] # Set the first column to be the ID column_name_mappings = OrderedDict([('id', 'ID')]) column_ids, add_column_name_mappings, columns_from_database = ColumnListSetting.return_columns( org_id, profile_id, view_klass_str) column_name_mappings.update(add_column_name_mappings) select_related = ['state', 'cycle'] ids = request.data.get('ids', []) filter_str = {'cycle': cycle_pk} if hasattr(view_klass, 'property'): select_related.append('property') prefetch_related = ['property__labels'] filter_str = {'property__organization_id': org_id} if ids: filter_str['property__id__in'] = ids # always export the labels column_name_mappings['property_labels'] = 'Property Labels' elif hasattr(view_klass, 'taxlot'): select_related.append('taxlot') prefetch_related = ['taxlot__labels'] filter_str = {'taxlot__organization_id': org_id} if ids: filter_str['taxlot__id__in'] = ids # always export the labels column_name_mappings['taxlot_labels'] = 'Tax Lot Labels' model_views = view_klass.objects.select_related(*select_related).prefetch_related(*prefetch_related).filter(**filter_str).order_by('id') # get the data in a dict which includes the related data data = TaxLotProperty.get_related(model_views, column_ids, columns_from_database) # add labels for i, record in enumerate(model_views): label_string = [] if hasattr(record, 'property'): for label in list(record.property.labels.all().order_by('name')): label_string.append(label.name) data[i]['property_labels'] = ','.join(label_string) elif hasattr(record, 'taxlot'): for label in list(record.taxlot.labels.all().order_by('name')): label_string.append(label.name) data[i]['taxlot_labels'] = ','.join(label_string) # force the data into the same order as the IDs if ids: order_dict = {obj_id: index for index, obj_id in enumerate(ids)} data.sort(key=lambda x: order_dict[x['id']]) # x is the property/taxlot object export_type = request.data.get('export_type', 'csv') filename = request.data.get('filename', f"ExportedData.{export_type}") if export_type == "csv": return self._csv_response(filename, data, column_name_mappings) elif export_type == "geojson": return self._json_response(filename, data, column_name_mappings)
def _get_filtered_results(self, request, profile_id): page = request.query_params.get('page', 1) per_page = request.query_params.get('per_page', 1) org_id = request.query_params.get('organization_id', None) cycle_id = request.query_params.get('cycle') # check if there is a query paramater for the profile_id. If so, then use that one profile_id = request.query_params.get('profile_id', profile_id) if not org_id: return JsonResponse( { 'status': 'error', 'message': 'Need to pass organization_id as query parameter' }, status=status.HTTP_400_BAD_REQUEST) if cycle_id: cycle = Cycle.objects.get(organization_id=org_id, pk=cycle_id) else: cycle = Cycle.objects.filter( organization_id=org_id).order_by('name') if cycle: cycle = cycle.first() else: return JsonResponse({ 'status': 'error', 'message': 'Could not locate cycle', 'pagination': { 'total': 0 }, 'cycle_id': None, 'results': [] }) # Return taxlot views limited to the 'inventory_ids' list. Otherwise, if selected is empty, return all if 'inventory_ids' in request.data and request.data['inventory_ids']: taxlot_views_list = TaxLotView.objects.select_related('taxlot', 'state', 'cycle') \ .filter(taxlot_id__in=request.data['inventory_ids'], taxlot__organization_id=org_id, cycle=cycle) \ .order_by('id') else: taxlot_views_list = TaxLotView.objects.select_related('taxlot', 'state', 'cycle') \ .filter(taxlot__organization_id=org_id, cycle=cycle) \ .order_by('id') paginator = Paginator(taxlot_views_list, per_page) try: taxlot_views = paginator.page(page) page = int(page) except PageNotAnInteger: taxlot_views = paginator.page(1) page = 1 except EmptyPage: taxlot_views = paginator.page(paginator.num_pages) page = paginator.num_pages org = Organization.objects.get(pk=org_id) # Retrieve all the columns that are in the db for this organization columns_from_database = Column.retrieve_all(org_id, 'taxlot', False) # This uses an old method of returning the show_columns. There is a new method that # is preferred in v2.1 API with the ProfileIdMixin. if profile_id is None: show_columns = None elif profile_id == -1: show_columns = list( Column.objects.filter(organization_id=org_id).values_list( 'id', flat=True)) else: try: profile = ColumnListSetting.objects.get( organization=org, id=profile_id, settings_location=VIEW_LIST, inventory_type=VIEW_LIST_TAXLOT) show_columns = list( ColumnListSettingColumn.objects.filter( column_list_setting_id=profile.id).values_list( 'column_id', flat=True)) except ColumnListSetting.DoesNotExist: show_columns = None related_results = TaxLotProperty.get_related(taxlot_views, show_columns, columns_from_database) # collapse units here so we're only doing the last page; we're already a # realized list by now and not a lazy queryset unit_collapsed_results = [ apply_display_unit_preferences(org, x) for x in related_results ] response = { 'pagination': { 'page': page, 'start': paginator.page(page).start_index(), 'end': paginator.page(page).end_index(), 'num_pages': paginator.num_pages, 'has_next': paginator.page(page).has_next(), 'has_previous': paginator.page(page).has_previous(), 'total': paginator.count }, 'cycle_id': cycle.id, 'results': unit_collapsed_results } return JsonResponse(response)
def unmerge(self, request, pk=None): """ Unmerge a taxlot view into two taxlot views --- parameters: - name: organization_id description: The organization_id for this user's organization required: true paramType: query """ try: old_view = TaxLotView.objects.select_related( 'taxlot', 'cycle', 'state').get(id=pk, taxlot__organization_id=self. request.GET['organization_id']) except TaxLotView.DoesNotExist: return { 'status': 'error', 'message': 'taxlot view with id {} does not exist'.format(pk) } # Duplicate pairing paired_view_ids = list( TaxLotProperty.objects.filter(taxlot_view_id=old_view.id).order_by( 'property_view_id').values_list('property_view_id', flat=True)) # Capture previous associated labels label_ids = list(old_view.labels.all().values_list('id', flat=True)) notes = old_view.notes.all() for note in notes: note.taxlot_view = None merged_state = old_view.state if merged_state.data_state != DATA_STATE_MATCHING or merged_state.merge_state != MERGE_STATE_MERGED: return { 'status': 'error', 'message': 'taxlot view with id {} is not a merged taxlot view'.format(pk) } log = TaxLotAuditLog.objects.select_related( 'parent_state1', 'parent_state2').filter( state=merged_state).order_by('-id').first() if log.parent_state1 is None or log.parent_state2 is None: return { 'status': 'error', 'message': 'taxlot view with id {} must have two parent states'.format(pk) } state1 = log.parent_state1 state2 = log.parent_state2 cycle_id = old_view.cycle_id # Clone the taxlot record twice old_taxlot = old_view.taxlot new_taxlot = old_taxlot new_taxlot.id = None new_taxlot.save() new_taxlot_2 = TaxLot.objects.get(pk=new_taxlot.pk) new_taxlot_2.id = None new_taxlot_2.save() # If the canonical TaxLot is NOT associated to another -View if not TaxLotView.objects.filter(taxlot_id=old_view.taxlot_id).exclude( pk=old_view.id).exists(): TaxLot.objects.get(pk=old_view.taxlot_id).delete() # Create the views new_view1 = TaxLotView(cycle_id=cycle_id, taxlot_id=new_taxlot.id, state=state1) new_view2 = TaxLotView(cycle_id=cycle_id, taxlot_id=new_taxlot_2.id, state=state2) # Mark the merged state as deleted merged_state.merge_state = MERGE_STATE_DELETE merged_state.save() # Change the merge_state of the individual states if log.parent1.name in ['Import Creation', 'Manual Edit' ] and log.parent1.import_filename is not None: # State belongs to a new record state1.merge_state = MERGE_STATE_NEW else: state1.merge_state = MERGE_STATE_MERGED if log.parent2.name in ['Import Creation', 'Manual Edit' ] and log.parent2.import_filename is not None: # State belongs to a new record state2.merge_state = MERGE_STATE_NEW else: state2.merge_state = MERGE_STATE_MERGED # In most cases data_state will already be 3 (DATA_STATE_MATCHING), but if one of the parents was a # de-duplicated record then data_state will be 0. This step ensures that the new states will be 3. state1.data_state = DATA_STATE_MATCHING state2.data_state = DATA_STATE_MATCHING state1.save() state2.save() # Delete the audit log entry for the merge log.delete() old_view.delete() new_view1.save() new_view2.save() # Asssociate labels label_objs = StatusLabel.objects.filter(pk__in=label_ids) new_view1.labels.set(label_objs) new_view2.labels.set(label_objs) # Duplicate notes to the new views for note in notes: created = note.created updated = note.updated note.id = None note.taxlot_view = new_view1 note.save() ids = [note.id] note.id = None note.taxlot_view = new_view2 note.save() ids.append(note.id) # Correct the created and updated times to match the original note Note.objects.filter(id__in=ids).update(created=created, updated=updated) for paired_view_id in paired_view_ids: TaxLotProperty(primary=True, cycle_id=cycle_id, taxlot_view_id=new_view1.id, property_view_id=paired_view_id).save() TaxLotProperty(primary=True, cycle_id=cycle_id, taxlot_view_id=new_view2.id, property_view_id=paired_view_id).save() return {'status': 'success', 'view_id': new_view1.id}
def pair_unpair_property_taxlot(property_id, taxlot_id, organization_id, pair): # TODO: validate against organization_id, make sure cycle_ids are the same try: property_view = PropertyView.objects.get(pk=property_id) except PropertyView.DoesNotExist: return JsonResponse( { 'status': 'error', 'message': 'property view with id {} does not exist'.format(property_id) }, status=status.HTTP_404_NOT_FOUND) try: taxlot_view = TaxLotView.objects.get(pk=taxlot_id) except TaxLotView.DoesNotExist: return JsonResponse( { 'status': 'error', 'message': 'tax lot view with id {} does not exist'.format(taxlot_id) }, status=status.HTTP_404_NOT_FOUND) pv_cycle = property_view.cycle_id tv_cycle = taxlot_view.cycle_id if pv_cycle != tv_cycle: return JsonResponse( { 'status': 'error', 'message': 'Cycle mismatch between PropertyView and TaxLotView' }, status=status.HTTP_400_BAD_REQUEST) if pair: string = 'pair' if TaxLotProperty.objects.filter(property_view_id=property_id, taxlot_view_id=taxlot_id).exists(): return JsonResponse({ 'status': 'success', 'message': 'taxlot {} and property {} are already {}ed'.format( taxlot_id, property_id, string) }) TaxLotProperty(primary=True, cycle_id=pv_cycle, property_view_id=property_id, taxlot_view_id=taxlot_id) \ .save() success = True else: string = 'unpair' if not TaxLotProperty.objects.filter( property_view_id=property_id, taxlot_view_id=taxlot_id).exists(): return JsonResponse({ 'status': 'success', 'message': 'taxlot {} and property {} are already {}ed'.format( taxlot_id, property_id, string) }) TaxLotProperty.objects.filter(property_view_id=property_id, taxlot_view_id=taxlot_id) \ .delete() success = True if success: return JsonResponse({ 'status': 'success', 'message': 'taxlot {} and property {} are now {}ed'.format( taxlot_id, property_id, string) }) else: return JsonResponse( { 'status': 'error', 'message': 'Could not {} because reasons, maybe bad organization id={}'. format(string, organization_id) }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def mapping_results(self, request, pk=None): """ Retrieves a paginated list of Properties and Tax Lots for an import file after mapping. """ import_file_id = pk org_id = request.query_params.get('organization_id', None) org = Organization.objects.get(pk=org_id) try: # get the field names that were in the mapping import_file = ImportFile.objects.get( pk=import_file_id, import_record__super_organization_id=org_id) except ImportFile.DoesNotExist: return JsonResponse( { 'status': 'error', 'message': 'Could not find import file with pk=' + str(pk) }, status=status.HTTP_400_BAD_REQUEST) # List of the only fields to show field_names = import_file.get_cached_mapped_columns # set of fields fields = { 'PropertyState': ['id', 'extra_data', 'lot_number'], 'TaxLotState': ['id', 'extra_data'] } columns_from_db = Column.retrieve_all(org_id) property_column_name_mapping = {} taxlot_column_name_mapping = {} for field_name in field_names: # find the field from the columns in the database for column in columns_from_db: if column['table_name'] == 'PropertyState' and \ field_name[0] == 'PropertyState' and \ field_name[1] == column['column_name']: property_column_name_mapping[ column['column_name']] = column['name'] if not column['is_extra_data']: fields['PropertyState'].append( field_name[1]) # save to the raw db fields continue elif column['table_name'] == 'TaxLotState' and \ field_name[0] == 'TaxLotState' and \ field_name[1] == column['column_name']: taxlot_column_name_mapping[ column['column_name']] = column['name'] if not column['is_extra_data']: fields['TaxLotState'].append( field_name[1]) # save to the raw db fields continue inventory_type = request.data.get('inventory_type', 'all') result = {'status': 'success'} if inventory_type == 'properties' or inventory_type == 'all': properties = PropertyState.objects.filter( import_file_id=import_file_id, data_state__in=[DATA_STATE_MAPPING, DATA_STATE_MATCHING], merge_state__in=[ MERGE_STATE_UNKNOWN, MERGE_STATE_NEW ]).only(*fields['PropertyState']).order_by('id') property_results = [] for prop in properties: prop_dict = TaxLotProperty.model_to_dict_with_mapping( prop, property_column_name_mapping, fields=fields['PropertyState'], exclude=['extra_data']) prop_dict.update( TaxLotProperty.extra_data_to_dict_with_mapping( prop.extra_data, property_column_name_mapping, fields=prop.extra_data.keys(), ).items()) prop_dict = apply_display_unit_preferences(org, prop_dict) property_results.append(prop_dict) result['properties'] = property_results if inventory_type == 'taxlots' or inventory_type == 'all': tax_lots = TaxLotState.objects.filter( import_file_id=import_file_id, data_state__in=[DATA_STATE_MAPPING, DATA_STATE_MATCHING], merge_state__in=[MERGE_STATE_UNKNOWN, MERGE_STATE_NEW ]).only(*fields['TaxLotState']).order_by('id') tax_lot_results = [] for tax_lot in tax_lots: tax_lot_dict = TaxLotProperty.model_to_dict_with_mapping( tax_lot, taxlot_column_name_mapping, fields=fields['TaxLotState'], exclude=['extra_data']) tax_lot_dict.update( TaxLotProperty.extra_data_to_dict_with_mapping( tax_lot.extra_data, taxlot_column_name_mapping, fields=tax_lot.extra_data.keys(), ).items()) tax_lot_dict = apply_display_unit_preferences( org, tax_lot_dict) tax_lot_results.append(tax_lot_dict) result['tax_lots'] = tax_lot_results return result