Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/1108 editable site location #1124

Merged
merged 3 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ shell:
@echo "------------------------------------------------------------------"
@docker-compose -p $(PROJECT_ID) exec django /bin/bash

dev-shell:
@echo
@echo "------------------------------------------------------------------"
@echo "Shelling in in development mode"
@echo "------------------------------------------------------------------"
@docker-compose -p $(PROJECT_ID) exec dev /bin/bash

db-bash:
@echo
@echo "------------------------------------------------------------------"
Expand Down
2 changes: 1 addition & 1 deletion django_project/minisass_frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 41 additions & 13 deletions django_project/monitor/admin.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import csv
from collections import OrderedDict
from django import forms
from django.conf import settings
from django.contrib import admin
from django.utils.encoding import smart_str
from django.contrib.gis import admin as geo_admin
from django.contrib.gis.geos import Point
from django.http import HttpResponse
from django.utils.encoding import smart_str
from django.utils.safestring import mark_safe
from leaflet.admin import LeafletGeoAdmin
from minisass_authentication.models import UserProfile
from monitor.forms import ObservationPestImageForm, CustomGeoAdminForm
from django.contrib.sites.models import Site

from monitor.forms import ObservationPestImageForm
from .models import (
Sites,
Observations,
Expand Down Expand Up @@ -174,7 +181,28 @@ class SiteImageInline(admin.TabularInline):


@admin.register(Sites)
class SitesAdmin(admin.ModelAdmin):
class SitesAdmin(geo_admin.OSMGeoAdmin):
form = CustomGeoAdminForm
class Media:
js = (
'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js', # Ensure jQuery is available
'admin/js/latlon_map_update.js', # Custom JS file to handle lat/lon updates
)

def latitude(self, obj):
return obj.the_geom.y if obj.the_geom else None

def longitude(self, obj):
return obj.the_geom.x if obj.the_geom else None

latitude.short_description = 'Latitude'
longitude.short_description = 'Longitude'

def render_change_form(self, request, context, *args, **kwargs):
context['osm_widget'] = True
return super().render_change_form(request, context, *args, **kwargs)


list_max_show_all = 1000
list_display = (
'site_name',
Expand Down Expand Up @@ -213,15 +241,15 @@ def download_selected_sites(self, request, queryset):
writer = csv.writer(response)
writer.writerow(
[
'Site Name',
'River Name',
'Description',
'Site Name',
'River Name',
'Description',
'River Category',
'Site Location',
'Created By',
'User Organization Name',
'User Expert Status',
'User Country',
'User Country',
'Site Creation Date'
])

Expand All @@ -237,14 +265,14 @@ def download_selected_sites(self, request, queryset):
user_is_expert = False
writer.writerow(
[
smart_str(site.site_name),
smart_str(site.river_name),
smart_str(site.description),
smart_str(site.river_cat),
smart_str(site.site_name),
smart_str(site.river_name),
smart_str(site.description),
smart_str(site.river_cat),
smart_str(f"Longitude: {site.the_geom.x}, Latitude: {site.the_geom.y}"),
smart_str(site.user.email),
smart_str(user_organization_name),
smart_str(user_is_expert),
smart_str(user_is_expert),
smart_str(user_country),
smart_str(site.time_stamp)
]
Expand All @@ -258,4 +286,4 @@ def download_selected_sites(self, request, queryset):


admin.site.register(ObservationPestImage)
admin.site.unregister(Site)
admin.site.unregister(Site)
29 changes: 29 additions & 0 deletions django_project/monitor/forms.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django import forms
from django.contrib.gis.geos import Point
from django.forms import ModelForm, Textarea, Select, DateInput

from monitor.models import Sites, Observations, ObservationPestImage
Expand Down Expand Up @@ -66,3 +67,31 @@ class MapForm(forms.Form):
edit_site = forms.CharField()
error = forms.CharField()
saved_obs = forms.CharField()


class CustomGeoAdminForm(forms.ModelForm):
latitude = forms.FloatField(required=False, label='Latitude')
longitude = forms.FloatField(required=False, label='Longitude')

class Meta:
model = Sites
fields = ['the_geom', 'latitude', 'longitude', 'site_name', 'river_name',
'description', 'river_cat', 'user', 'assessment']

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

# Pre-fill lat/lng from the existing point field if available
if self.instance and self.instance.the_geom:
self.fields['latitude'].initial = self.instance.the_geom.y
self.fields['longitude'].initial = self.instance.the_geom.x

def clean(self):
cleaned_data = super().clean()
lat = cleaned_data.get('latitude')
lng = cleaned_data.get('longitude')

if lat is not None and lng is not None:
cleaned_data['the_geom'] = Point(lng, lat)

return cleaned_data
107 changes: 107 additions & 0 deletions django_project/monitor/static/admin/js/latlon_map_update.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
(function($) {
$(document).ready(function() {
var $latField = $('#id_latitude');
var $lonField = $('#id_longitude');
var mapWidget = window.geodjango_the_geom.map;

if (!mapWidget) {
return;
}

// Helper function to find the vector layer containing the point
function getVectorLayer() {
var vectorLayer = null;

// Loop through all layers in the map to find a vector layer
for (var i = 0; i < mapWidget.layers.length; i++) {
var layer = mapWidget.layers[i];
if (layer.CLASS_NAME === 'OpenLayers.Layer.Vector') {
vectorLayer = layer;
break;
}
}

return vectorLayer;
}

var vectorLayer = getVectorLayer();

// If the vector layer exists, attach event listeners to it
if (vectorLayer) {
// Listen for changes to features (points) on the vector layer
vectorLayer.events.on({
'featuremodified': function(event) {
var geometry = event.feature.geometry;
var lonLat = new OpenLayers.LonLat(geometry.x, geometry.y).transform(
mapWidget.getProjectionObject(),
new OpenLayers.Projection("EPSG:4326") // To WGS84
);

// Update lat/lon fields based on new point location
$latField.val(lonLat.lat.toFixed(8));
$lonField.val(lonLat.lon.toFixed(8));
},
'featureadded': function(event) {
var geometry = event.feature.geometry;
var lonLat = new OpenLayers.LonLat(geometry.x, geometry.y).transform(
mapWidget.getProjectionObject(),
new OpenLayers.Projection("EPSG:4326") // To WGS84
);

// Update lat/lon fields when a new point is added
$latField.val(lonLat.lat.toFixed(8));
$lonField.val(lonLat.lon.toFixed(8));
}
});
}

// Update the map based on lat/lon fields when they are changed
function updateMapFromFields() {
vectorLayer.removeAllFeatures();
var lat = parseFloat($latField.val());
var lon = parseFloat($lonField.val());

if (!isNaN(lat) && !isNaN(lon)) {
var lonLat = new OpenLayers.LonLat(lon, lat).transform(
new OpenLayers.Projection("EPSG:4326"), // From WGS84
mapWidget.getProjectionObject() // To map projection
);

var pointGeometry = new OpenLayers.Geometry.Point(lonLat.lon, lonLat.lat);

// Update the existing feature with new geometry
if (vectorLayer.features.length > 0) {
vectorLayer.features[0].geometry = pointGeometry;
vectorLayer.drawFeature(vectorLayer.features[0]);
} else {
// Add a new feature if none exist
var feature = new OpenLayers.Feature.Vector(pointGeometry);
vectorLayer.addFeatures([feature]);
}

// Recenter the map to the new location
mapWidget.setCenter(lonLat, mapWidget.getZoom());
}
}

// Debounce function: delays execution until user stops typing
function debounce(func, delay) {
var timeout;
return function() {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
func.apply(context, args);
}, delay);
};
}

// Attach debounced event listeners to the lat/lon fields
var debouncedUpdateMap = debounce(updateMapFromFields, 500); // 500ms delay

// Attach event listeners to the lat/lon fields to update the map when they change
$latField.on('input', debouncedUpdateMap);
$lonField.on('input', debouncedUpdateMap);

});
})(django.jQuery);