Esempio n. 1
0
    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)
Esempio n. 2
0
    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)
Esempio n. 3
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
Esempio n. 4
0
    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)
Esempio n. 5
0
    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)
Esempio n. 6
0
    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)
Esempio n. 7
0
    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
Esempio n. 8
0
    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)
Esempio n. 9
0
    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
Esempio n. 10
0
    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