コード例 #1
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
コード例 #2
0
ファイル: taxlots.py プロジェクト: luciawerneck/seed
    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)
コード例 #3
0
ファイル: test_taxlot_views.py プロジェクト: riahtu/seed
    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'])
コード例 #4
0
ファイル: taxlots.py プロジェクト: luciawerneck/seed
    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'}
コード例 #5
0
ファイル: taxlots.py プロジェクト: luciawerneck/seed
    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}
コード例 #6
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 = ['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)
コード例 #7
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 = 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)
コード例 #8
0
    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}
コード例 #9
0
ファイル: properties.py プロジェクト: hong-hanh-dang/seed
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)
コード例 #10
0
ファイル: import_files.py プロジェクト: riahtu/seed
    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