Adding Markers to OpenStreetMap's Django/Python
I'm having issues with leaflet, and OpenStreetMap I cannot get a marker to be added for each job available. it just shows up as a blank map. Debugging shows that the information is being sent correctly but the map is not update. Here is the debug info. The list view works perfectly. and the map shows a correct marker when clicking on job from list view.
(0.001) SELECT "users_job"."id", "users_job"."title", "users_job"."description", "users_job"."city", "users_job"."state", "users_job"."zip", "users_job"."latitude", "users_job"."longitude" FROM "users_job"; args=(); alias=default
Jobs List: [{'id': 1, 'title': 'test job', 'description': 'this is a test', 'city': '*redacted*', 'state': '*redacted*', 'zip': '*redacted*', 'latitude': *redacted*, 'longitude': *redacted*}, {'id': 2, 'title': 'testjob2', 'description': 'This is a test', 'city': '*redacted*', 'state': '*redacted*', 'zip': '*redacted*', 'latitude': *redacted*, 'longitude': *redacted*}, {'id': 3, 'title': 'test job json', 'description': 'json test', 'city': '*redacted*', 'state': '*redacted*', 'zip': '*redacted*', 'latitude': *redacted*, 'longitude': *redacted*}, {'id': 4, 'title': 'jsontest2', 'description': 'asdofmasodfm', 'city': '*redacted*', 'state': '*redacted*', 'zip': '*redacted*', 'latitude': *redacted*, 'longitude': *redacted*}]
(0.002) SELECT "django_session"."session_key", "django_session"."session_data", "django_session"."expire_date" FROM "django_session" WHERE ("django_session"."expire_date" > '2025-03-11T01:18:30.939183+00:00'::timestamptz AND "django_session"."session_key" = '*redacted*') LIMIT 21; args=(datetime.datetime(2025, 3, 11, 1, 18, 30, 939183, tzinfo=datetime.timezone.utc), '*redacted*'); alias=default
(0.002) SELECT "users_user"."id", "users_user"."password", "users_user"."last_login", "users_user"."is_superuser", "users_user"."username", "users_user"."is_staff", "users_user"."is_active", "users_user"."date_joined", "users_user"."email", "users_user"."phone_number", "users_user"."address", "users_user"."city", "users_user"."state", "users_user"."zip", "users_user"."latitude", "users_user"."longitude", "users_user"."avatar", "users_user"."user_type", "users_user"."first_name", "users_user"."last_name" FROM "users_user" WHERE "users_user"."id" = 2 LIMIT 21; args=(2,); alias=default
"GET / HTTP/1.1" 200 8512
{% extends "users/base.html" %}
{% block title %} Home Page {% endblock title %}
{% block content %}
<div class="container py-5">
<!-- Toggle Button -->
<div class="btn-group" role="group" aria-label="View Toggle">
<button id="map-view-btn" type="button" class="btn btn-primary">Map View</button>
<button id="list-view-btn" type="button" class="btn btn-secondary">List View</button>
</div>
<!-- Map View -->
<div id="map-view" style="height: 400px; width: 100%; display: none;">
<div id="map" style="height: 100%; width: 100%;"></div>
</div>
<div class="container py-5">
<div id="debug-info" style="display: none; border: 1px solid #ccc; padding: 10px; margin-top: 20px;">
<h5>Debug Information</h5>
<pre id="debug-content"></pre>
</div>
</div>
<!-- List View -->
<div id="list-view" style="display: none;">
<ul class="list-group">
{% for job in jobs %}
{% if job.id %}
<li class="list-group-item">
<h5><a href="{% url 'job_detail' job.id %}">{{ job.title }}</a></h5>
</li>
{% else %}
<li class="list-group-item">Invalid job data: {{ job|safe }}</li> <!-- Debugging statement -->
{% endif %}
{% empty %}
<li class="list-group-item">No jobs available</li>
{% endfor %}
</ul>
</div>
<script id="job-data" type="application/json">
{{ jobs|json_script:"job-data" }}
</script>
<!-- Hidden element to store user's location -->
<script>
document.addEventListener('DOMContentLoaded', function() {
var mapViewBtn = document.getElementById('map-view-btn');
var listViewBtn = document.getElementById('list-view-btn');
var mapView = document.getElementById('map-view');
var listView = document.getElementById('list-view');
var debugInfo = document.getElementById('debug-info');
var debugContent = document.getElementById('debug-content');
// Show map view by default
mapView.style.display = 'block';
mapViewBtn.addEventListener('click', function() {
mapView.style.display = 'block';
listView.style.display = 'none';
mapViewBtn.classList.add('btn-primary');
mapViewBtn.classList.remove('btn-secondary');
listViewBtn.classList.add('btn-secondary');
listViewBtn.classList.remove('btn-primary');
});
listViewBtn.addEventListener('click', function() {
mapView.style.display = 'none';
listView.style.display = 'block';
mapViewBtn.classList.add('btn-secondary');
mapViewBtn.classList.remove('btn-primary');
listViewBtn.classList.add('btn-primary');
listViewBtn.classList.remove('btn-secondary');
});
// Set initial location to the geographical center of the United States
var initialLocation = [37.0902, -95.7129]; // Latitude and Longitude of the geographical center of the US
var initialZoom = 4; // Zoom level to show the entire US
var map = L.map('map').setView(initialLocation, initialZoom);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
var jobs = JSON.parse(document.getElementById('job-data').textContent); // Ensure jobs data is passed as a safe JSON object
console.log("Jobs data:", jobs); // Debugging statement to log jobs data
if (jobs.length === 0) {
console.warn("No jobs available");
}
jobs.forEach(function(job) {
console.log("Processing job:", job); // Debugging statement to log each job
var jobLocation = [job.latitude, job.longitude];
console.log("Job location:", jobLocation); // Debugging statement to log job location
if (job.latitude !== 0 && job.longitude !== 0) { // Ensure valid coordinates
L.marker(jobLocation).addTo(map)
.bindPopup('<b>' + job.title + '</b><br>' + job.zip);
} else {
console.warn("Invalid coordinates for job:", job); // Warn about invalid coordinates
}
});
// Display debug information in the container
debugContent.textContent = 'Jobs data: ' + JSON.stringify(jobs, null, 2);
debugInfo.style.display = 'block';
});
</script>
{% endblock content %}
Here is the Job form
# Job Form
class JobForm(forms.ModelForm):
title = forms.CharField(max_length=100, widget=forms.TextInput(attrs={'class': 'form-control'}))
description = forms.CharField(widget=forms.Textarea(attrs={'class': 'form-control'}))
address = forms.CharField(max_length=300, widget=forms.TextInput(attrs={'class': 'form-control'}))
city = forms.CharField(max_length=100, widget=forms.TextInput(attrs={'class': 'form-control'}))
state = forms.CharField(max_length=25, widget=forms.Select(choices=STATE_CHOICES, attrs={'class': 'form-control state-select'})) # Add custom class
zip = forms.CharField(max_length=12, widget=forms.TextInput(attrs={'class': 'form-control'}))
budget = forms.DecimalField(widget=forms.NumberInput(attrs={'class': 'form-control'}))
deadline = forms.DateField(widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}))
document = forms.FileField(widget=forms.ClearableFileInput(attrs={'class': 'form-control'}), required=False)
image = forms.ImageField(widget=forms.ClearableFileInput(attrs={'class': 'form-control'}), required=False)
class Meta:
model = Job
fields = ['title', 'description', 'address', 'city', 'state', 'zip', 'budget', 'deadline', 'document', 'image']
def save(self, commit=True):
job = super().save(commit=False)
if job.zip:
job.latitude, job.longitude = job.get_lat_lng_from_zip(job.zip)
if commit:
job.save()
return job
Here is the job model
# Job Model
class Job(models.Model):
id = models.BigAutoField(primary_key=True)
customer = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
description = models.TextField(max_length=1000, default='No description provided')
address = models.CharField(max_length=255, blank=True, null=True)
city = models.CharField(max_length=100, blank=True, null=True)
state = models.CharField(max_length=25, blank=True, null=True) # Add the state field
zip = models.CharField(max_length=10, default='11111')
latitude = models.FloatField(default=0)
longitude = models.FloatField(default=0)
budget = models.DecimalField(max_digits=12, decimal_places=2)
deadline = models.DateField(default='2021-12-31')
created_at = models.DateTimeField(auto_now_add=True)
document = models.FileField(upload_to='job_documents/', blank=True, null=True)
image = models.ImageField(upload_to='job_images/', blank=True, null=True)
def save(self, *args, **kwargs):
if self.zip:
lat, lng = self.get_lat_lng_from_zip(self.zip)
if lat is not None and lng is not None:
self.latitude = lat
self.longitude = lng
else:
logger.error(f"Failed to get latitude and longitude for zip code: {self.zip}")
if self.city and not self.state:
self.state, lat, lng = self.get_state_and_coords_from_city(self.city, self.zip)
if lat is not None and lng is not None:
self.latitude = lat
self.longitude = lng
super().save(*args, **kwargs)
def get_lat_lng_from_zip(self, zip_code):
url = f'https://nominatim.openstreetmap.org/search?postalcode={zip_code}&format=json&countrycodes=us'
headers = {
'User-Agent': 'ConstructionConnect/1.0 (jonnytombstone@gmail.com)' # Replace with your app name and email
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status() # Raise an HTTPError for bad responses
data = response.json()
logger.debug(f"Response data for zip code {zip_code}: {data}") # Log the response data
if data:
location = data[0]
lat = float(location.get('lat', 0))
lon = float(location.get('lon', 0))
logger.debug(f"Extracted lat: {lat}, lon: {lon} for zip code {zip_code}")
return lat, lon
except requests.RequestException as e:
logger.error(f"Error fetching latitude and longitude for zip code {zip_code}: {e}")
return None, None
Here is the Job View
def home(request):
jobs = Job.objects.all()
jobs_list = list(jobs.values('id', 'title', 'description', 'city', 'state', 'zip', 'latitude', 'longitude'))
# Log the jobs_list data for debugging
logger.debug('Jobs List: %s', jobs_list)
# Job Views
@login_required
def job_detail(request, job_id):
job = get_object_or_404(Job, id=job_id)
comments = job.comments.all()
if request.method == 'POST':
comment_form = CommentForm(request.POST)
if comment_form.is_valid():
comment = comment_form.save(commit=False)
comment.job = job
comment.author = request.user
comment.save()
return redirect('job_detail', job_id=job.id)
else:
comment_form = CommentForm()
context = {
'job': job,
'comments': comments,
'comment_form': comment_form,
}
return render(request, 'users/job_detail.html', context)