From d4add1012b5ed473e317ff5add9cbc770b6e4799 Mon Sep 17 00:00:00 2001 From: David Pollard Date: Wed, 6 Nov 2024 18:52:47 -0800 Subject: [PATCH 01/13] Enable shape file exports of a users property --- .gitignore | 8 ++ landmapper/app/static/landmapper/js/report.js | 43 +++++++++++ .../landmapper/report/report-overview.html | 4 + landmapper/app/urls.py | 1 + landmapper/app/views.py | 76 ++++++++++++++++++- landmapper/landmapper/settings.py | 5 ++ 6 files changed, 133 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 75cf1aa..b92e8a3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,11 @@ Vagrantfile .vagrant *local_settings.py landmapper/app/testing_files +*/shapefiles +*/.ipynb_checkpoints +*/.idea +*/.git +*/.gitignore +*/.gitattributes +*/.gitmodules +*/.gitkeep \ No newline at end of file diff --git a/landmapper/app/static/landmapper/js/report.js b/landmapper/app/static/landmapper/js/report.js index c664dbe..b6a8cf4 100644 --- a/landmapper/app/static/landmapper/js/report.js +++ b/landmapper/app/static/landmapper/js/report.js @@ -29,4 +29,47 @@ let newPropertyID = documentPropertyIdSplit.join('%7C'); if (copyToAccountBtn) { copyToAccountBtn.href = `/landmapper/report/${newPropertyID}`; } + + + /** + * Add event listener to the export layer button + */ + function exportLayerHandler() { + const propertyId = this.getAttribute('data-property-id'); + fetch(`/export_layer/${propertyId}/shp`, { + method: 'GET', + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }) + .then(response => response.ok ? response.blob() : response.text().then(text => { throw new Error(text); })) + .then(blob => { + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = `${propertyId}.zip`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + }) + .catch(error => { + console.error('Error:', error); + alert('An error occurred while exporting the layer.'); + }); + } + + function setupExportLayerButton() { + const exportLayerButton = document.getElementById('export-layer-button'); + if (exportLayerButton) { + exportLayerButton.addEventListener('click', exportLayerHandler); + } + } + + function init() { + setupExportLayerButton(); + } + + document.addEventListener('DOMContentLoaded', init); + })(); \ No newline at end of file diff --git a/landmapper/app/templates/landmapper/report/report-overview.html b/landmapper/app/templates/landmapper/report/report-overview.html index e4543c4..63cc3f6 100644 --- a/landmapper/app/templates/landmapper/report/report-overview.html +++ b/landmapper/app/templates/landmapper/report/report-overview.html @@ -25,6 +25,10 @@ Copy To My Properties {% endif %} + + + + {% endif %}

{{ property_name }}

diff --git a/landmapper/app/urls.py b/landmapper/app/urls.py index 40e17ed..a75b8ad 100644 --- a/landmapper/app/urls.py +++ b/landmapper/app/urls.py @@ -46,6 +46,7 @@ path('admin_export_property_records/', exportPropertyRecords, name='export_property_records'), path('accounts/profile/', homeRedirect, name='account_confirm_email'), path('auth/email/', homeRedirect, name='auth_email'), + path('export_layer//shp', export_layer, name='export_layer'), # path('tinymce/', include('tinymce.urls')), re_path(r'^tinymce/', include('tinymce.urls')), ] diff --git a/landmapper/app/views.py b/landmapper/app/views.py index 094dee3..abd24f3 100644 --- a/landmapper/app/views.py +++ b/landmapper/app/views.py @@ -27,13 +27,18 @@ from django.views.decorators.csrf import csrf_protect from django.views.generic.edit import FormView from flatblocks.models import FlatBlock +import os from PIL import Image import requests import ssl +import subprocess import sys from urllib.error import URLError from urllib.parse import quote import urllib.request, urllib.parse +import logging + +logger = logging.getLogger(__name__) def unstable_request_wrapper(url, params=False, retries=0): # """ @@ -714,8 +719,39 @@ def get_property_pdf_georef(request, property_id, map_type="aerial"): pass return response -## BELONGS IN VIEWS.py -def export_layer(request): +#* +#* Export shapefile +#* +def export_shapefile(db_user, db_pw_command, database_name, shpdir, filename, query): + """ + Helper function to export a shapefile using pgsql2shp. + """ + export_command = f"pgsql2shp -u {db_user}{db_pw_command} -f {shpdir}/{filename} {database_name} \"{query}\"" + subprocess.run(export_command, shell=True, check=True) + +#* +#* Zip Shapefile +#* +def zip_shapefile(shpdir, filename): + """ + Helper function to zip the shapefile. + """ + import io + import zipfile + + os.chdir(shpdir) + files = [f for f in os.listdir(shpdir) if f.startswith(filename)] + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, mode="w", compression=zipfile.ZIP_DEFLATED) as zf: + for file in files: + zf.write(file) + zip_buffer.seek(0) + return zip_buffer + +#* +#* Export Layer +#* +def export_layer(request, property_id): ''' (called on request for download GIS data) IN: @@ -723,11 +759,43 @@ def export_layer(request): Format (default: zipped .shp, leave modular to support json & others) property OUT: - layer file + property layer in requested format USES: pgsql2shp (OGR/PostGIS built-in) ''' - return render(request, 'landmapper/base.html', {}) + if not request.user.is_authenticated: + return HttpResponse('User not authenticated. Please log in.', status=401) + + try: + property_record = properties.get_property_by_id(property_id, request.user) + except PropertyRecord.DoesNotExist: + return HttpResponse('Property not found or you do not have permission to access it.', status=404) + + db_user = settings.DATABASES['default']['USER'] + db_pw_command = f" -P {settings.DATABASES['default']['PASSWORD']}" if settings.DATABASES['default']['PASSWORD'] else "" + database_name = settings.DATABASES['default']['NAME'] + filename = f"{property_record.name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + shpdir = os.path.join(settings.SHAPEFILE_EXPORT_DIR, filename) + os.makedirs(shpdir, exist_ok=True) + + try: + query = f"SELECT * FROM app_propertyrecord WHERE id = {property_record.id};" + export_shapefile(db_user, db_pw_command, database_name, shpdir, filename, query) + zip_buffer = zip_shapefile(shpdir, filename) + + # Return zipped shapefile + response = HttpResponse(zip_buffer.read(), content_type='application/zip') + response['Content-Disposition'] = f'attachment; filename={filename}.zip' + return response + except subprocess.CalledProcessError as e: + logger.error(f"Error exporting shapefile: {e}") + return HttpResponse('Error exporting shapefile.', status=500) + finally: + # Clean up temporary files + for file in os.listdir(shpdir): + os.remove(os.path.join(shpdir, file)) + os.rmdir(shpdir) + ### REGISTRATION/ACCOUNT MANAGEMENT ### def accountsRedirect(request): diff --git a/landmapper/landmapper/settings.py b/landmapper/landmapper/settings.py index 31dc580..ec8b1a6 100644 --- a/landmapper/landmapper/settings.py +++ b/landmapper/landmapper/settings.py @@ -1509,6 +1509,11 @@ TESTING_DIR = os.path.join(APP_DIR, 'testing_files') IMAGE_TEST_DIR = os.path.join(TESTING_DIR, 'image_test') +########################################### +## Shapefile Export ### +########################################### +SHAPEFILE_EXPORT_DIR = os.path.join(APP_DIR, 'static/landmapper/shapefiles/') + ########################################### ## PDF Files ### ########################################### From 2a1b83dad171934850fd2340cee50ceaa807121e Mon Sep 17 00:00:00 2001 From: David Pollard Date: Thu, 7 Nov 2024 16:49:07 -0800 Subject: [PATCH 02/13] Hide shapefile dir from git --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b92e8a3..358bf3e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ Vagrantfile .vagrant *local_settings.py landmapper/app/testing_files -*/shapefiles +shapefiles/* */.ipynb_checkpoints */.idea */.git From a2b31085199c002f174c230ad29ff8a7a4865e33 Mon Sep 17 00:00:00 2001 From: David Pollard Date: Thu, 7 Nov 2024 16:49:21 -0800 Subject: [PATCH 03/13] Hide shapefile dir from git --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 358bf3e..bf23439 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ Vagrantfile .vagrant *local_settings.py landmapper/app/testing_files -shapefiles/* +*/shapefiles/* */.ipynb_checkpoints */.idea */.git From 9799596c8160c7dfa5e03436fa9a3b2d4d22e348 Mon Sep 17 00:00:00 2001 From: David Pollard Date: Thu, 7 Nov 2024 16:49:32 -0800 Subject: [PATCH 04/13] Hide shapefile dir from git --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bf23439..87abbb1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ Vagrantfile .vagrant *local_settings.py landmapper/app/testing_files -*/shapefiles/* +*/shapefiles/ */.ipynb_checkpoints */.idea */.git From da019bab7b755c0ca2b8493dc8f21efeb37b52f6 Mon Sep 17 00:00:00 2001 From: David Pollard Date: Thu, 7 Nov 2024 16:49:47 -0800 Subject: [PATCH 05/13] Hide shapefile dir from git --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 87abbb1..4a6ef3f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ Vagrantfile .vagrant *local_settings.py landmapper/app/testing_files -*/shapefiles/ +landmapper/app/static/landmapper/shapefiles/ */.ipynb_checkpoints */.idea */.git From 26735372ec7237400550dc723ea8e06069d05aca Mon Sep 17 00:00:00 2001 From: David Pollard Date: Thu, 7 Nov 2024 16:50:32 -0800 Subject: [PATCH 06/13] Remove gitignore files added by copilot --- .gitignore | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 4a6ef3f..dac0034 100644 --- a/.gitignore +++ b/.gitignore @@ -8,11 +8,4 @@ Vagrantfile .vagrant *local_settings.py landmapper/app/testing_files -landmapper/app/static/landmapper/shapefiles/ -*/.ipynb_checkpoints -*/.idea -*/.git -*/.gitignore -*/.gitattributes -*/.gitmodules -*/.gitkeep \ No newline at end of file +landmapper/app/static/landmapper/shapefiles/ \ No newline at end of file From 45bae970e756a61133f7db45c3d1d6a519c2d622 Mon Sep 17 00:00:00 2001 From: David Pollard Date: Thu, 7 Nov 2024 17:35:32 -0800 Subject: [PATCH 07/13] Make export shp button style match other buttons plus a new icon --- .../app/static/landmapper/img/icon/icon-shp.svg | 8 ++++++++ .../templates/landmapper/report/report-overview.html | 12 ++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 landmapper/app/static/landmapper/img/icon/icon-shp.svg diff --git a/landmapper/app/static/landmapper/img/icon/icon-shp.svg b/landmapper/app/static/landmapper/img/icon/icon-shp.svg new file mode 100644 index 0000000..3f60627 --- /dev/null +++ b/landmapper/app/static/landmapper/img/icon/icon-shp.svg @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/landmapper/app/templates/landmapper/report/report-overview.html b/landmapper/app/templates/landmapper/report/report-overview.html index 63cc3f6..f8b0cfe 100644 --- a/landmapper/app/templates/landmapper/report/report-overview.html +++ b/landmapper/app/templates/landmapper/report/report-overview.html @@ -25,10 +25,6 @@ Copy To My Properties {% endif %} - - - - {% endif %}

{{ property_name }}

@@ -71,6 +67,14 @@

All maps of your property

PDF + + {% if property.user_id == user_id %} + + + {% endif %} {% else %} From d1a415cf94f4ecafbae412303219cb9581810b8d Mon Sep 17 00:00:00 2001 From: David Pollard Date: Fri, 8 Nov 2024 12:51:08 -0800 Subject: [PATCH 08/13] Improved shp icon --- .../app/static/landmapper/img/icon/icon-shp.svg | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/landmapper/app/static/landmapper/img/icon/icon-shp.svg b/landmapper/app/static/landmapper/img/icon/icon-shp.svg index 3f60627..79e6673 100644 --- a/landmapper/app/static/landmapper/img/icon/icon-shp.svg +++ b/landmapper/app/static/landmapper/img/icon/icon-shp.svg @@ -1,8 +1,6 @@ - - - - - - \ No newline at end of file + + \ No newline at end of file From 2f1a37935edbb01b0fbbbf6880afeacb87c44509 Mon Sep 17 00:00:00 2001 From: David Pollard Date: Fri, 8 Nov 2024 12:51:37 -0800 Subject: [PATCH 09/13] New share icon and slightly smaller icons --- landmapper/app/static/landmapper/css/report.css | 2 +- landmapper/app/templates/landmapper/report/report-share.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/landmapper/app/static/landmapper/css/report.css b/landmapper/app/static/landmapper/css/report.css index 66f7974..e241e27 100644 --- a/landmapper/app/static/landmapper/css/report.css +++ b/landmapper/app/static/landmapper/css/report.css @@ -128,7 +128,7 @@ p { .icon { display: block; margin: .25em auto; - width: 2em; + width: 1.5em; /* vertical-align: text-bottom; */ } diff --git a/landmapper/app/templates/landmapper/report/report-share.html b/landmapper/app/templates/landmapper/report/report-share.html index 86ce0bd..8d8410a 100644 --- a/landmapper/app/templates/landmapper/report/report-share.html +++ b/landmapper/app/templates/landmapper/report/report-share.html @@ -1,6 +1,6 @@ {% load static %} \ No newline at end of file From 6ed21110eda54d572b218a21a0c7afb8ab9dd12b Mon Sep 17 00:00:00 2001 From: David Pollard Date: Fri, 8 Nov 2024 12:51:47 -0800 Subject: [PATCH 10/13] New share icon and slightly smaller icons --- landmapper/app/static/landmapper/img/icon/icon-share.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 landmapper/app/static/landmapper/img/icon/icon-share.svg diff --git a/landmapper/app/static/landmapper/img/icon/icon-share.svg b/landmapper/app/static/landmapper/img/icon/icon-share.svg new file mode 100644 index 0000000..454579f --- /dev/null +++ b/landmapper/app/static/landmapper/img/icon/icon-share.svg @@ -0,0 +1 @@ + \ No newline at end of file From c14a3d54da824711fdf71457866ac26531ae0c63 Mon Sep 17 00:00:00 2001 From: David Pollard Date: Fri, 8 Nov 2024 12:52:41 -0800 Subject: [PATCH 11/13] Slightly larger report icons --- landmapper/app/static/landmapper/css/report.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/landmapper/app/static/landmapper/css/report.css b/landmapper/app/static/landmapper/css/report.css index e241e27..fd27987 100644 --- a/landmapper/app/static/landmapper/css/report.css +++ b/landmapper/app/static/landmapper/css/report.css @@ -128,7 +128,7 @@ p { .icon { display: block; margin: .25em auto; - width: 1.5em; + width: 1.75em; /* vertical-align: text-bottom; */ } From 19ba115a0798028a7345e8972dc64ac3f79f9aa8 Mon Sep 17 00:00:00 2001 From: David Pollard Date: Fri, 8 Nov 2024 12:58:08 -0800 Subject: [PATCH 12/13] Closes limit shapefile export button to logged-in users --- .../templates/landmapper/report/report-overview.html | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/landmapper/app/templates/landmapper/report/report-overview.html b/landmapper/app/templates/landmapper/report/report-overview.html index f8b0cfe..6a5d868 100644 --- a/landmapper/app/templates/landmapper/report/report-overview.html +++ b/landmapper/app/templates/landmapper/report/report-overview.html @@ -68,13 +68,11 @@

All maps of your property

PDF - {% if property.user_id == user_id %} - - - {% endif %} + + {% else %} From c40db487812e95ea54f8056bc68cc4ba4656ff1b Mon Sep 17 00:00:00 2001 From: David Pollard Date: Fri, 8 Nov 2024 13:06:31 -0800 Subject: [PATCH 13/13] Closes Add decorator to the API to restrict shapefile exports to logged in users only --- landmapper/app/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/landmapper/app/views.py b/landmapper/app/views.py index abd24f3..1497d64 100644 --- a/landmapper/app/views.py +++ b/landmapper/app/views.py @@ -722,6 +722,7 @@ def get_property_pdf_georef(request, property_id, map_type="aerial"): #* #* Export shapefile #* +@login_required(login_url='/auth/login/') def export_shapefile(db_user, db_pw_command, database_name, shpdir, filename, query): """ Helper function to export a shapefile using pgsql2shp.