Skip to content

Commit

Permalink
Merge pull request #3114 from SEED-platform/2976-export-properties-pr…
Browse files Browse the repository at this point in the history
…ogress-bar

2976 export properties progress bar
  • Loading branch information
Ryo committed Feb 11, 2022
2 parents 4b5a878 + 4e49106 commit 9df4672
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ angular.module('BE.seed.controller.export_inventory_modal', []).controller('expo
'$scope',
'$uibModalInstance',
'user_service',
'uploader_service',
'ids',
'columns',
'inventory_type',
Expand All @@ -18,6 +19,7 @@ angular.module('BE.seed.controller.export_inventory_modal', []).controller('expo
$scope,
$uibModalInstance,
user_service,
uploader_service,
ids,
columns,
inventory_type,
Expand All @@ -30,27 +32,40 @@ angular.module('BE.seed.controller.export_inventory_modal', []).controller('expo
$scope.include_label_header = false;
$scope.inventory_type = inventory_type;
$scope.exporting = false
$scope.exporter_progress = {
progress: 0,
status_message: '',
};

$scope.export_selected = function (export_type) {
var filename = $scope.export_name;
var ext = '.' + export_type;
if (!_.endsWith(filename, ext)) filename += ext;
spinner_utility.show()
$scope.exporting = true
return $http.post('/api/v3/tax_lot_properties/export/', {
ids: ids,
filename: filename,
profile_id: profile_id,
export_type: export_type,
include_notes: $scope.include_notes
}, {
$http.get('/api/v3/tax_lot_properties/start_export/', {
params: {
organization_id: user_service.get_organization().id,
inventory_type: inventory_type
},
responseType: export_type === 'xlsx' ? 'arraybuffer' : undefined
}
}).then(data => {
uploader_service.check_progress_loop(data.data.progress_key, 0, 1,
function () { },
function () { },
$scope.exporter_progress)
return $http.post('/api/v3/tax_lot_properties/export/', {
ids: ids,
filename: filename,
profile_id: profile_id,
export_type: export_type,
include_notes: $scope.include_notes,
progress_key: data.data.progress_key
}, {
params: {
organization_id: user_service.get_organization().id,
inventory_type: inventory_type
},
responseType: export_type === 'xlsx' ? 'arraybuffer' : undefined
})
}).then(function (response) {
spinner_utility.hide()
var blob_type = response.headers()['content-type'];
var data;
if (export_type === 'xlsx') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,7 @@ angular.module('BE.seed.controller.inventory_list', [])
$uibModal.open({
templateUrl: urls.static_url + 'seed/partials/export_inventory_modal.html',
controller: 'export_inventory_modal_controller',
backdrop: 'static',
resolve: {
ids: function () {
var viewId = $scope.inventory_type === 'properties' ? 'property_view_id' : 'taxlot_view_id';
Expand Down
5 changes: 5 additions & 0 deletions seed/static/seed/partials/export_inventory_modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ <h4 class="modal-title" id="exportModalLabel" translate>Export your Properties a
<button type="button" class="btn btn-primary" ng-click="export_selected('xlsx')" ng-disabled="!export_name.length || exporting" ng-if="inventory_type === 'properties'" translate>Export BuildingSync in Excel format</button>
<button type="button" class="btn btn-primary" ng-click="export_selected('geojson')" ng-disabled="!export_name.length || exporting" translate>Export GeoJSON</button>
</div>
<div class="progress_bar_container export_progress" ng-if="exporting">
<div class="progress_bar_copy_top">Progress: </div>
<uib-progressbar class="progress-striped active" value="exporter_progress.progress" type="success"></uib-progressbar>
<div class="progress_bar_copy_bottom">{$ exporter_progress.progress | number:0 $}% {$:: 'Complete' | translate $} {$ exporter_progress.status_message ? ': ' + exporter_progress.status_message : '' $}</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" ng-click="cancel()" ng-disabled="exporting"translate>Cancel</button>
</div>
4 changes: 3 additions & 1 deletion seed/static/seed/scss/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1989,7 +1989,9 @@ div[sd-uploader][disabled], button[sd-uploader][disabled] {
padding-top: 10px;
margin-top: 10px;
}

.export_progress {
padding: 0 20px 0 20px;
}
.btn-sm, .btn-xs {
font-size: 13px;
}
Expand Down
1 change: 1 addition & 0 deletions seed/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,7 @@ def test_get_property_multiple_taxlots(self):
self.assertEqual(rcycle['organization'], self.org.pk)

self.assertEqual(len(results['taxlots']), 2)
results['taxlots'].sort(key=lambda x: x['id'])

rtaxlot_view_1 = results['taxlots'][0]
self.assertEqual(rtaxlot_view_1['id'], taxlot_view_1.pk)
Expand Down
41 changes: 38 additions & 3 deletions seed/views/v3/tax_lot_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import datetime
import io
from collections import OrderedDict
import math
from random import randint

import xlsxwriter
from django.http import JsonResponse, HttpResponse
Expand All @@ -24,6 +26,7 @@

from seed.decorators import ajax_request_class
from seed.lib.superperms.orgs.decorators import has_perm_class
from seed.lib.progress_data.progress_data import ProgressData
from seed.models import (
PropertyView,
TaxLotProperty,
Expand All @@ -45,6 +48,7 @@
)
from seed.utils.api import api_endpoint_class, OrgMixin
from seed.utils.api_schema import AutoSchemaHelper
from seed.utils.match import update_sub_progress_total

INVENTORY_MODELS = {'properties': PropertyView, 'taxlots': TaxLotView}

Expand All @@ -71,12 +75,14 @@ class TaxLotPropertyViewSet(GenericViewSet, OrgMixin):
'ids': ['integer'],
'filename': 'string',
'export_type': 'string',
'profile_id': 'integer'
'profile_id': 'integer',
'proress_key': 'string',
},
description='- ids: (View) IDs for records to be exported\n'
'- filename: desired filename including extension (defaulting to \'ExportedData.{export_type}\')\n'
'- export_types: \'csv\', \'geojson\', \'xlsx\' (defaulting to \'csv\')\n'
'- profile_id: Column List Profile ID to use for customizing fields included in export'
'- progress_key: (Optional) Used to find and update the ProgressData object. If none is provided, a ProgressData object will be created.'
),
)
@api_endpoint_class
Expand All @@ -88,6 +94,15 @@ def export(self, request):
Download a collection of the TaxLot and Properties in multiple formats.
"""
org_id = self.get_organization(request)

if request.data.get('progress_key'):
progress_key = request.data['progress_key']
progress_data = ProgressData.from_key(progress_key)
else:
progress_data = ProgressData(func_name='export_inventory', unique_id=org_id)
progress_key = progress_data.key
progress_data = update_sub_progress_total(100, progress_key)

profile_id = None
column_profile = None
if 'profile_id' in request.data and str(request.data['profile_id']) not in ['None', '']:
Expand Down Expand Up @@ -130,13 +145,16 @@ def export(self, request):
*prefetch_related).filter(**filter_str).order_by('id')

# get the data in a dict which includes the related data
progress_data.step('Exporting Inventory...')
data = TaxLotProperty.serialize(model_views, column_ids, columns_from_database)

derived_columns = column_profile.derived_columns.all() if column_profile is not None else []
column_name_mappings.update({dc.name: dc.name for dc in derived_columns})
progress_data.step('Exporting Inventory...')

# add labels, notes, and derived columns
include_notes = request.data.get('include_notes', True)
batch_size = math.ceil(len(model_views) / 98)
for i, record in enumerate(model_views):
label_string = []
note_string = []
Expand All @@ -160,6 +178,9 @@ def export(self, request):
for derived_column in derived_columns:
data[i][derived_column.name] = derived_column.evaluate(inventory_state=record.state)

if batch_size > 0 and i % batch_size == 0:
progress_data.step('Exporting Inventory...')

# force the data into the same order as the IDs
if ids:
order_dict = {obj_id: index for index, obj_id in enumerate(ids)}
Expand All @@ -168,18 +189,32 @@ def export(self, request):
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}")

progress_data.finish_with_success()
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)

@api_endpoint_class
@ajax_request_class
@has_perm_class('requires_member')
@action(detail=False, methods=['GET'])
def start_export(self, request):
"""
Generate a ProgressData object that will be used to monitor property and tax lot exports
"""
org_id = self.get_organization(request)

progress_data = ProgressData(func_name='export_inventory', unique_id=f'{org_id}{randint(10000, 99999)}')
progress_key = progress_data.key
progress_data = update_sub_progress_total(100, progress_key)
return progress_data.result()

def _csv_response(self, filename, data, column_name_mappings):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
Expand Down

0 comments on commit 9df4672

Please sign in to comment.