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': [] }) property_views_list = PropertyView.objects.select_related('property', 'state', 'cycle') \ .filter(property__organization_id=request.query_params['organization_id'], cycle=cycle).order_by('id') paginator = Paginator(property_views_list, per_page) try: property_views = paginator.page(page) page = int(page) except PageNotAnInteger: property_views = paginator.page(1) page = 1 except EmptyPage: property_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(property_views, columns) } return JsonResponse(response, encoder=PintJSONEncoder)
def test_tax_lot_property_get_related(self): """Test to make sure get_related returns the fields""" for i in range(50): p = self.property_view_factory.get_property_view() self.properties.append(p.id) qs_filter = {"pk__in": self.properties} qs = PropertyView.objects.filter(**qs_filter) columns = [ 'address_line_1', 'generation_date', 'energy_alerts', 'space_alerts', 'building_count', 'owner', 'source_eui', 'jurisdiction_tax_lot_id', 'city', 'confidence', 'district', 'best_guess_confidence', 'site_eui', 'building_certification', 'modified', 'match_type', 'source_eui_weather_normalized', u'id', 'property_name', 'conditioned_floor_area', 'pm_property_id', 'use_description', 'source_type', 'year_built', 'release_date', 'gross_floor_area', 'owner_city_state', 'owner_telephone', 'recent_sale_date', ] columns_from_database = Column.retrieve_all(self.org.id, 'property', False) data = TaxLotProperty.get_related(qs, columns, columns_from_database) self.assertEqual(len(data), 50) self.assertEqual(len(data[0]['related']), 0)
def taxlots_across_cycles(org_id, profile_id, cycle_ids=[]): # Identify column preferences to be used to scope fields/values columns_from_database = Column.retrieve_all(org_id, 'taxlot', False) if 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_id=org_id, 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 results = {} for cycle_id in cycle_ids: # get -Views for this Cycle taxlot_views = TaxLotView.objects.select_related('taxlot', 'state', 'cycle') \ .filter(taxlot__organization_id=org_id, cycle_id=cycle_id) \ .order_by('id') related_results = TaxLotProperty.get_related(taxlot_views, show_columns, columns_from_database) org = Organization.objects.get(pk=org_id) unit_collapsed_results = [ apply_display_unit_preferences(org, x) for x in related_results ] results[cycle_id] = unit_collapsed_results return results
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 = ['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 = ['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.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.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) elif export_type == "xlsx": return self._spreadsheet_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 = ColumnListProfile.objects.get( organization=org, id=profile_id, profile_location=VIEW_LIST, inventory_type=VIEW_LIST_TAXLOT) show_columns = list( ColumnListProfileColumn.objects.filter( column_list_profile_id=profile.id).values_list( 'column_id', flat=True)) except ColumnListProfile.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 _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=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 columns_from_database = Column.retrieve_all(org_id, 'taxlot', False) related_results = TaxLotProperty.get_related(taxlot_views, 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 org = Organization.objects.get(pk=org_id) 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 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 """ 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' }) columns = request.data.get('columns', None) if columns is None: # default the columns for now if no columns are passed columns = [ 'pm_property_id', 'pm_parent_property_id', 'tax_jurisdiction_tax_lot_id', 'ubid', 'custom_id_1', 'tax_custom_id_1', 'city', 'state', 'postal_code', 'tax_primary', 'property_name', 'campus', 'gross_floor_area', 'use_description', 'energy_score', 'site_eui', 'property_notes', 'property_type', 'year_ending', 'owner', 'owner_email', 'owner_telephone', 'building_count', 'year_built', 'recent_sale_date', 'conditioned_floor_area', 'occupied_floor_area', 'owner_address', 'owner_city_state', 'owner_postal_code', 'home_energy_score_id', 'generation_date', 'release_date', 'source_eui_weather_normalized', 'site_eui_weather_normalized', 'source_eui', 'energy_alerts', 'space_alerts', 'building_certification', 'number_properties', 'block_number', 'district', 'BLDGS', 'property_state_id', 'taxlot_state_id', 'property_view_id', 'taxlot_view_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] # Grab all the columns and create a column name lookup col_inventory_type = 'property' if view_klass_str == 'properties' else 'taxlot' columns_db = Column.retrieve_all( request.query_params['organization_id'], col_inventory_type, False) column_lookup = {} for c in columns_db: column_lookup[c['name']] = c['displayName'] # make the csv header header = [] for c in columns: if c in column_lookup: header.append(column_lookup[c]) else: header.append(c) 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': request.query_params['organization_id'] } if ids: filter_str['property__id__in'] = ids # always export the labels columns += ['property_labels'] header.append('Property Labels') elif hasattr(view_klass, 'taxlot'): select_related.append('taxlot') filter_str = { 'taxlot__organization_id': request.query_params['organization_id'] } if ids: filter_str['taxlot__id__in'] = ids # always export the labels columns += ['taxlot_labels'] header.append('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, columns) # 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 # note that the labels are in the property_labels column and are returned by the # TaxLotProperty.get_related method. # header writer.writerow(header) # iterate over the results to preserve column order and write row. # The front end returns columns with prepended tax_ and property_ columns for the # related fields. This is an expensive operation and can cause issues with stripping # off property_ from items such as property_name, property_notes, and property_type # which are explicitly excluded below for datum in data: row = [] for column in columns: if column in [ 'property_name', 'property_notes', 'property_type', 'property_labels' ]: row.append(datum.get(column, None)) elif column.startswith('tax_'): # There are times when there are duplicate column names in tax/property if datum.get('related') and len(datum['related']) > 0: # Looks like related returns a list. Is this as expected? row.append(datum['related'][0].get( re.sub(r'^tax_', '', column), None)) else: row.append(None) elif column.startswith('property_'): # There are times when there are duplicate column names in tax/property if datum.get('related') and len(datum['related']) > 0: # Looks like related returns a list. Is this as expected? row.append(datum['related'][0].get( re.sub(r'^property_', '', column), None)) else: row.append(None) else: # check if the data is in the normal section of the result, if not, then try to grab it from # the related section. There shouldn't be any duplicates here because the former two # if methods will grab those instances. result = datum.get(column, None) if not result: if datum.get('related'): result = datum['related'][0].get(column, None) row.append(result) writer.writerow(row) return response
def export(self, request): """ Download a collection of the TaxLot and Properties in multiple formats. """ 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 = ColumnListProfile.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 = ['labels'] filter_str = {'property__organization_id': org_id} if ids: filter_str['id__in'] = ids # always export the labels and notes column_name_mappings['property_notes'] = 'Property Notes' column_name_mappings['property_labels'] = 'Property Labels' elif hasattr(view_klass, 'taxlot'): select_related.append('taxlot') prefetch_related = ['labels'] filter_str = {'taxlot__organization_id': org_id} if ids: filter_str['id__in'] = ids # always export the labels and notes column_name_mappings['taxlot_notes'] = 'Tax Lot Notes' 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 and notes for i, record in enumerate(model_views): label_string = [] note_string = [] for label in list(record.labels.all().order_by('name')): label_string.append(label.name) for note in list(record.notes.all().order_by('created')): note_string.append(note.created.astimezone().strftime( "%Y-%m-%d %I:%M:%S %p") + "\n" + note.text) if hasattr(record, 'property'): data[i]['property_labels'] = ','.join(label_string) data[i]['property_notes'] = '\n----------\n'.join(note_string) elif hasattr(record, 'taxlot'): data[i]['taxlot_labels'] = ','.join(label_string) data[i]['taxlot_notes'] = '\n----------\n'.join(note_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)} if view_klass_str == 'properties': view_id_str = 'property_view_id' else: view_id_str = 'taxlot_view_id' data.sort(key=lambda inventory_obj: order_dict[inventory_obj[ view_id_str]]) 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) elif export_type == "xlsx": return self._spreadsheet_response(filename, data, column_name_mappings)
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 """ 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' }) columns = request.data.get('columns', None) if columns is None: # default the columns for now if no columns are passed columns = Column.retrieve_db_fields( request.query_params['organization_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] # Grab all the columns and create a column name lookup col_inventory_type = 'property' if view_klass_str == 'properties' else 'taxlot' columns_db = Column.retrieve_all( request.query_params['organization_id'], col_inventory_type, False) column_lookup = {} db_column_name_lookup = {} column_related_lookup = {} for c in columns_db: column_lookup[c['name']] = c['display_name'] db_column_name_lookup[c['name']] = c['column_name'] column_related_lookup[c['name']] = c['related'] # add a couple of other Display Names column_lookup['notes_count'] = 'Notes Count' column_lookup['id'] = 'ID' # make the csv header header = [] for c in columns: if c in column_lookup: header.append(column_lookup[c]) else: header.append(c) 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': request.query_params['organization_id'] } if ids: filter_str['property__id__in'] = ids # always export the labels columns += ['property_labels'] header.append('Property Labels') elif hasattr(view_klass, 'taxlot'): select_related.append('taxlot') filter_str = { 'taxlot__organization_id': request.query_params['organization_id'] } if ids: filter_str['taxlot__id__in'] = ids # always export the labels columns += ['taxlot_labels'] header.append('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, db_column_name_lookup.values(), columns_db) # 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 # note that the labels are in the property_labels column and are returned by the # TaxLotProperty.get_related method. # header writer.writerow(header) # iterate over the results to preserve column order and write row. for datum in data: row = [] for column in columns: row_result = None if column in column_related_lookup and column_related_lookup[ column]: # this is a related column, grab out of the related section if datum.get('related'): row_result = datum['related'][0].get(column, None) else: row_result = datum.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 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