def retrieve(self, request, pk): """ Get taxlot details --- parameters: - name: pk description: The primary key of the TaxLotView required: true paramType: path - name: organization_id description: The organization_id for this user's organization required: true paramType: query """ result = self._get_taxlot_view(pk) if result.get('status', None) != 'error': taxlot_view = result.pop('taxlot_view') result.update(TaxLotViewSerializer(taxlot_view).data) # remove TaxLotView id from result result.pop('id') result['state'] = TaxLotStateSerializer(taxlot_view.state).data result['properties'] = self._get_properties(taxlot_view.pk) result['history'], master = self.get_history(taxlot_view) result = update_result_with_master(result, master) return JsonResponse(result, status=status.HTTP_200_OK) else: return JsonResponse(result, status=status.HTTP_404_NOT_FOUND)
def links(self, request, pk=None): """ Get taxlot details for each linked taxlot across org cycles """ organization_id = request.query_params.get('organization_id', None) base_view = TaxLotView.objects.select_related('cycle').filter( pk=pk, cycle__organization_id=organization_id ) if base_view.exists(): result = {'data': []} linked_views = TaxLotView.objects.select_related('cycle').filter( taxlot_id=base_view.get().taxlot_id, cycle__organization_id=organization_id ).order_by('-cycle__start') for linked_view in linked_views: state_data = TaxLotStateSerializer(linked_view.state).data state_data['cycle_id'] = linked_view.cycle.id state_data['view_id'] = linked_view.id result['data'].append(state_data) return JsonResponse(result, status=status.HTTP_200_OK) else: result = { 'status': 'error', 'message': 'property view with id {} does not exist in given organization'.format(pk) } return JsonResponse(result)
def get_history(self, taxlot_view): """Return history in reverse order""" # access the history from the property state history, master = taxlot_view.state.history() # convert the history and master states to StateSerializers master['state'] = TaxLotStateSerializer(master['state_data']).data del master['state_data'] del master['state_id'] for h in history: h['state'] = TaxLotStateSerializer(h['state_data']).data del h['state_data'] del h['state_id'] return history, master
def record_dict(log): filename = None if not log.import_filename else path.basename( log.import_filename) if filename: # Attempt to remove NamedTemporaryFile suffix name, ext = path.splitext(filename) pattern = re.compile('(.*?)(_[a-zA-Z0-9]{7})$') match = pattern.match(name) if match: filename = match.groups()[0] + ext return { 'state': TaxLotStateSerializer(log.state).data, 'date_edited': convert_to_js_timestamp(log.created), 'source': log.get_record_type_display(), 'filename': filename, # 'changed_fields': json.loads(log.description) if log.record_type == AUDIT_USER_EDIT else None }
def retrieve(self, request, pk): """ Get taxlot details """ result = self._get_taxlot_view(pk) if result.get('status', None) != 'error': taxlot_view = result.pop('taxlot_view') result.update(TaxLotViewSerializer(taxlot_view).data) # remove TaxLotView id from result result.pop('id') result['state'] = TaxLotStateSerializer(taxlot_view.state).data result['properties'] = self._get_properties(taxlot_view.pk) result['history'], master = self.get_history(taxlot_view) result = update_result_with_master(result, master) return JsonResponse(result, status=status.HTTP_200_OK) else: return JsonResponse(result, status=status.HTTP_404_NOT_FOUND)
def retrieve(self, request, pk): """ Get property details --- parameters: - name: cycle_id description: The cycle id for filtering the taxlot view required: true paramType: query - name: organization_id description: The organization_id for this user's organization required: true paramType: query """ 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' }) result = self._get_taxlot_view(pk, cycle_pk) if result.get('status', None) != 'error': taxlot_view = result.pop('taxlot_view') result.update(TaxLotViewSerializer(taxlot_view).data) # remove TaxLotView id from result result.pop('id') result['state'] = TaxLotStateSerializer(taxlot_view.state).data result['properties'] = self._get_properties(taxlot_view.pk) result['history'], master = self.get_history(taxlot_view) result = update_result_with_master(result, master) status_code = status.HTTP_200_OK else: status_code = status.HTTP_404_NOT_FOUND return JsonResponse(result, status=status_code)
def update(self, request, pk): """ Update a taxlot --- parameters: - name: cycle_id description: The cycle id for filtering the taxlot view required: true paramType: query """ data = request.data 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' }) result = self._get_taxlot_view(pk, cycle_pk) if result.get('status', None) != 'error': taxlot_view = result.pop('taxlot_view') taxlot_state_data = TaxLotStateSerializer(taxlot_view.state).data new_taxlot_state_data = data['state'] changed = True for key, val in new_taxlot_state_data.iteritems(): if val == '': new_taxlot_state_data[key] = None changed_fields = get_changed_fields(taxlot_state_data, new_taxlot_state_data) if not changed_fields: changed = False if not changed: result.update({ 'status': 'error', 'message': 'Nothing to update' }) status_code = 422 # status.HTTP_422_UNPROCESSABLE_ENTITY else: log = TaxLotAuditLog.objects.select_related().filter( state=taxlot_view.state).order_by('-id').first() if 'extra_data' in new_taxlot_state_data.keys(): taxlot_state_data['extra_data'].update( new_taxlot_state_data.pop('extra_data')) taxlot_state_data.update(new_taxlot_state_data) if log.name == 'Import Creation': # Add new state taxlot_state_data.pop('id') new_taxlot_state_serializer = TaxLotStateSerializer( data=taxlot_state_data) if new_taxlot_state_serializer.is_valid(): new_state = new_taxlot_state_serializer.save() taxlot_view.state = new_state taxlot_view.save() TaxLotAuditLog.objects.create( organization=log.organization, parent1=log, parent2=None, parent_state1=log.state, parent_state2=None, state=new_state, name='Manual Edit', description=None, import_filename=log.import_filename, record_type=AUDIT_USER_EDIT) result.update({ 'state': new_taxlot_state_serializer.validated_data }) # Removing organization key AND import_file key because they're not JSON-serializable # TODO find better solution result['state'].pop('organization') result['state'].pop('import_file') status_code = status.HTTP_201_CREATED else: result.update({ 'status': 'error', 'message': 'Invalid Data' }) status_code = 422 # status.HTTP_422_UNPROCESSABLE_ENTITY elif log.name in [ 'Manual Edit', 'Manual Match', 'System Match', 'Merge current state in migration' ]: # Override previous edit state or merge state state = taxlot_view.state for key, value in new_taxlot_state_data.iteritems(): setattr(state, key, value) state.save() result.update({'state': TaxLotStateSerializer(state).data}) # Removing organization key AND import_file key because they're not JSON-serializable # TODO find better solution result['state'].pop('organization') result['state'].pop('import_file') status_code = status.HTTP_201_CREATED else: result = { 'status': 'error', 'message': 'Unrecognized audit log name: ' + log.name } status_code = 422 return JsonResponse(result, status=status_code) else: status_code = status.HTTP_404_NOT_FOUND return JsonResponse(result, status=status_code)
def get_history(self, taxlot_view): """Return history in reverse order.""" history = [] def record_dict(log): filename = None if not log.import_filename else path.basename( log.import_filename) if filename: # Attempt to remove NamedTemporaryFile suffix name, ext = path.splitext(filename) pattern = re.compile('(.*?)(_[a-zA-Z0-9]{7})$') match = pattern.match(name) if match: filename = match.groups()[0] + ext return { 'state': TaxLotStateSerializer(log.state).data, 'date_edited': convert_to_js_timestamp(log.created), 'source': log.get_record_type_display(), 'filename': filename, # 'changed_fields': json.loads(log.description) if log.record_type == AUDIT_USER_EDIT else None } log = TaxLotAuditLog.objects.select_related( 'state', 'parent1', 'parent2').filter( state_id=taxlot_view.state_id).order_by('-id').first() master = { 'state': TaxLotStateSerializer(log.state).data, 'date_edited': convert_to_js_timestamp(log.created), } # Traverse parents and add to history if log.name in [ 'Manual Match', 'System Match', 'Merge current state in migration' ]: done_searching = False while not done_searching: if (log.parent1_id is None and log.parent2_id is None) or log.name == 'Manual Edit': done_searching = True elif log.name == 'Merge current state in migration': record = record_dict(log.parent1) history.append(record) if log.parent1.name == 'Import Creation': done_searching = True else: tree = log.parent1 log = tree else: tree = None if log.parent2: if log.parent2.name in [ 'Import Creation', 'Manual Edit' ]: record = record_dict(log.parent2) history.append(record) elif log.parent2.name == 'System Match' and log.parent2.parent1.name == 'Import Creation' and \ log.parent2.parent2.name == 'Import Creation': # Handle case where an import file matches within itself, and proceeds to match with # existing records record = record_dict(log.parent2.parent2) history.append(record) record = record_dict(log.parent2.parent1) history.append(record) else: tree = log.parent2 if log.parent1.name in ['Import Creation', 'Manual Edit']: record = record_dict(log.parent1) history.append(record) else: tree = log.parent1 if not tree: done_searching = True else: log = tree elif log.name == 'Manual Edit': record = record_dict(log.parent1) history.append(record) elif log.name == 'Import Creation': record = record_dict(log) history.append(record) return history, master
def update(self, request, pk): """ Update a taxlot and run the updated record through a match and merge round within it's current Cycle. --- parameters: - name: organization_id description: The organization_id for this user's organization required: true paramType: query """ data = request.data result = self._get_taxlot_view(pk) if result.get('status', 'error') != 'error': taxlot_view = result.pop('taxlot_view') taxlot_state_data = TaxLotStateSerializer(taxlot_view.state).data # get the taxlot state information from the request new_taxlot_state_data = data['state'] # set empty strings to None for key, val in new_taxlot_state_data.items(): if val == '': new_taxlot_state_data[key] = None changed_fields, previous_data = get_changed_fields( taxlot_state_data, new_taxlot_state_data) if not changed_fields: result.update({ 'status': 'success', 'message': 'Records are identical' }) return JsonResponse(result, status=status.HTTP_204_NO_CONTENT) else: # Not sure why we are going through the pain of logging this all right now... need to # reevaluate this. log = TaxLotAuditLog.objects.select_related().filter( state=taxlot_view.state).order_by('-id').first() # if checks above pass, create an exact copy of the current state for historical purposes if log.name == 'Import Creation': # Add new state by removing the existing ID. taxlot_state_data.pop('id') # Remove the import_file_id for the first edit of a new record # If the import file has been deleted and this value remains the serializer won't be valid taxlot_state_data.pop('import_file') new_taxlot_state_serializer = TaxLotStateSerializer( data=taxlot_state_data) if new_taxlot_state_serializer.is_valid(): # create the new property state, and perform an initial save / moving relationships new_state = new_taxlot_state_serializer.save() # then assign this state to the property view and save the whole view taxlot_view.state = new_state taxlot_view.save() TaxLotAuditLog.objects.create( organization=log.organization, parent1=log, parent2=None, parent_state1=log.state, parent_state2=None, state=new_state, name='Manual Edit', description=None, import_filename=log.import_filename, record_type=AUDIT_USER_EDIT) result.update( {'state': new_taxlot_state_serializer.data}) # save the property view so that the datetime gets updated on the property. taxlot_view.save() else: result.update({ 'status': 'error', 'message': 'Invalid update data with errors: {}'.format( new_taxlot_state_serializer.errors) }) return JsonResponse( result, status=status.HTTP_422_UNPROCESSABLE_ENTITY) # redo assignment of this variable in case this was an initial edit taxlot_state_data = TaxLotStateSerializer( taxlot_view.state).data if 'extra_data' in new_taxlot_state_data: taxlot_state_data['extra_data'].update( new_taxlot_state_data['extra_data']) taxlot_state_data.update({ k: v for k, v in new_taxlot_state_data.items() if k != 'extra_data' }) log = TaxLotAuditLog.objects.select_related().filter( state=taxlot_view.state).order_by('-id').first() if log.name in [ 'Manual Edit', 'Manual Match', 'System Match', 'Merge current state in migration' ]: # Convert this to using the serializer to save the data. This will override the # previous values in the state object. # Note: We should be able to use partial update here and pass in the changed # fields instead of the entire state_data. updated_taxlot_state_serializer = TaxLotStateSerializer( taxlot_view.state, data=taxlot_state_data) if updated_taxlot_state_serializer.is_valid(): # create the new property state, and perform an initial save / moving # relationships updated_taxlot_state_serializer.save() result.update( {'state': updated_taxlot_state_serializer.data}) # save the property view so that the datetime gets updated on the property. taxlot_view.save() Note.create_from_edit(request.user.id, taxlot_view, new_taxlot_state_data, previous_data) merge_count, link_count, view_id = match_merge_link( taxlot_view.id, 'TaxLotState') result.update({ 'view_id': view_id, 'match_merged_count': merge_count, 'match_link_count': link_count, }) return JsonResponse(result, status=status.HTTP_200_OK) else: result.update({ 'status': 'error', 'message': 'Invalid update data with errors: {}'.format( updated_taxlot_state_serializer.errors) }) return JsonResponse( result, status=status.HTTP_422_UNPROCESSABLE_ENTITY) else: result = { 'status': 'error', 'message': 'Unrecognized audit log name: ' + log.name } return JsonResponse(result, status=status.HTTP_204_NO_CONTENT) else: return JsonResponse(result, status=status.HTTP_404_NOT_FOUND)
def update(self, request, pk): """ Update a taxlot --- parameters: - name: organization_id description: The organization_id for this user's organization required: true paramType: query """ data = request.data result = self._get_taxlot_view(pk) if result.get('status', 'error') != 'error': taxlot_view = result.pop('taxlot_view') taxlot_state_data = TaxLotStateSerializer(taxlot_view.state).data # get the taxlot state information from the request new_taxlot_state_data = data['state'] # set empty strings to None for key, val in new_taxlot_state_data.iteritems(): if val == '': new_taxlot_state_data[key] = None changed_fields = get_changed_fields(taxlot_state_data, new_taxlot_state_data) if not changed_fields: result.update({ 'status': 'success', 'message': 'Records are identical' }) return JsonResponse(result, status=status.HTTP_204_NO_CONTENT) else: # Not sure why we are going through the pain of logging this all right now... need to # reevaluate this. log = TaxLotAuditLog.objects.select_related().filter( state=taxlot_view.state).order_by('-id').first() if 'extra_data' in new_taxlot_state_data.keys(): taxlot_state_data['extra_data'].update( new_taxlot_state_data.pop('extra_data')) taxlot_state_data.update(new_taxlot_state_data) if log.name == 'Import Creation': # Add new state by removing the existing ID. taxlot_state_data.pop('id') new_taxlot_state_serializer = TaxLotStateSerializer( data=taxlot_state_data) if new_taxlot_state_serializer.is_valid(): # create the new property state, and perform an initial save / moving relationships new_state = new_taxlot_state_serializer.save() # then assign this state to the property view and save the whole view taxlot_view.state = new_state taxlot_view.save() TaxLotAuditLog.objects.create( organization=log.organization, parent1=log, parent2=None, parent_state1=log.state, parent_state2=None, state=new_state, name='Manual Edit', description=None, import_filename=log.import_filename, record_type=AUDIT_USER_EDIT) result.update( {'state': new_taxlot_state_serializer.data}) return JsonResponse(result, status=status.HTTP_200_OK) else: result.update({ 'status': 'error', 'message': 'Invalid update data with errors: {}'.format( new_taxlot_state_serializer.errors) }) return JsonResponse( result, status=status.HTTP_422_UNPROCESSABLE_ENTITY) elif log.name in [ 'Manual Edit', 'Manual Match', 'System Match', 'Merge current state in migration' ]: # Convert this to using the serializer to save the data. This will override the previous values # in the state object. # Note: We should be able to use partial update here and pass in the changed fields instead of the # entire state_data. updated_taxlot_state_serializer = TaxLotStateSerializer( taxlot_view.state, data=taxlot_state_data) if updated_taxlot_state_serializer.is_valid(): # create the new property state, and perform an initial save / moving relationships updated_taxlot_state_serializer.save() result.update( {'state': updated_taxlot_state_serializer.data}) return JsonResponse(result, status=status.HTTP_200_OK) else: result.update({ 'status': 'error', 'message': 'Invalid update data with errors: {}'.format( updated_taxlot_state_serializer.errors) }) return JsonResponse( result, status=status.HTTP_422_UNPROCESSABLE_ENTITY) else: result = { 'status': 'error', 'message': 'Unrecognized audit log name: ' + log.name } return JsonResponse(result, status=status.HTTP_204_NO_CONTENT) # save the tax lot view, even if it hasn't changed so that the datetime gets updated on the taxlot. # Uhm, does this ever get called? There are a bunch of returns in the code above. taxlot_view.save() else: return JsonResponse(result, status=status.HTTP_404_NOT_FOUND) return JsonResponse(result, status=status.HTTP_404_NOT_FOUND)