How to center a Leaflet Map forms in a Django template - django

after 2 days trying to do it by myself, I need some help please
I'm using GeoDjango and Leaflet. I have a model "listing" having a field location as PointField, my form uses LeafletWidget as per below.
As it is, it is working but when I create a new listing, there is nothing in location, so it shows a default world map.
I would like in my template to setup the following:
CENTER: ({{ user.lat }}, {{ user.lng }}) and zoom: 10
since I know that the New listing will be in the same geographical area as the user.
And I have NO idea how to do it!!!
Model.py
location = models.PointField(null=True, blank=True)
forms.py
from leaflet.forms.fields import PointField
from leaflet.forms.widgets import LeafletWidget
...
LEAFLET_WIDGET_ATTRS = {
'map_height': '600px',
'map_width': '50%',
'display_raw': 'true',
'map_srid': 4326,
}
...
class ListingForm(forms.ModelForm):
required_css_class = 'required'
...
location = forms.PointField(
widget=LeafletWidget(attrs=LEAFLET_WIDGET_ATTRS))
...
template.html
<!-- form start -->
<form action="{% url 'listing_new' %}" method="POST" class="listing-form" role="form" novalidate enctype="multipart/form-data" id="listingform">
{% csrf_token %}
<div class="box-body">
{{ form.non_field_errors }}
{% for field in listingform %}
<div class="fieldWrapper form-group {% if field.errors %} field_error{% endif %} ">
<label for="{{ field.id_for_label }}">
{{ field.label }}{% if field.field.required %}<span class="required-asterisk">*</span>{% endif %}
</label>
{{ field }}
{% for error in field.errors %}
<span class="help-block">{{ error }}</span>
{% endfor %}
</div>
{% endfor %}
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary" form="listingform">Submit</button>
</div>
</form>
I tried to use the following:
<script type="text/javascript">
window.addEventListener("map:init", function (e) {
var detail = e.detail;
detail.options.djoptions['center']=[{{ listing_lat }}, {{ listing_lng }}];
detail.options.djoptions['zoom']=10;
console.log(detail.options);
console.log(detail.loadmap);
}, false);
</script>
BUT it doesn't modify the code auto-generated by the leaflet widget:
<div id="id_location-map" class="leaflet-container-default"></div>
<script type="text/javascript">
(function () {
function loadmap() {
var djoptions = {"srid": null, "extent": [[-90, -180], [90, 180]], "fitextent": true, "center": null, "zoom": null, "minzoom": null, "maxzoom": null, "layers": [["OSM", "//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", "\u00a9 OpenStreetMap contributors"]], "overlays": [], "attributionprefix": null, "scale": "metric", "minimap": false, "resetview": true, "tilesextent": []},
options = {djoptions: djoptions, initfunc: loadmap,
globals: false, callback: id_location_map_callback},
map = L.Map.djangoMap('id_location-map', options);
}
var loadevents = ["load"];
if (loadevents.length === 0) loadmap();
else if (window.addEventListener) for (var i=0; i<loadevents.length; i++) window.addEventListener(loadevents[i], loadmap, false);
else if (window.jQuery) jQuery(window).on(loadevents.join(' '), loadmap);
})();
</script>

So, the solution is to add this piece of JS in the template:
<script type="text/javascript">
window.addEventListener("map:init", function (e) {
var detail = e.detail;
detail.map.setView([{{user_lat}},{{user_lng}}], 10);
}, false);
</script>

in simple JS, you'd set it up like:
map.setView([{{ user.lat }}, {{ user.lng }}], 10);
But with GeoDjango, you could try:
LEAFLET_WIDGET_ATTRS = {
'map_height': '600px',
'map_width': '50%',
'display_raw': 'true',
'map_srid': 4326,
}
LEAFLET_CONFIG {
'DEFAULT_CENTER': ({{ user.lat }}, {{ user.lng }}),
'DEFAULT_ZOOM': 10,
}
See this documentation: https://buildmedia.readthedocs.org/media/pdf/django-leaflet/latest/django-leaflet.pdf

In your Django.settings files add these lines:
LEAFLET_CONFIG = { 'DEFAULT_CENTER': (41.25 ,20), # Latitude , Longitude
'DEFAULT_ZOOM': 8,
'MAX_ZOOM': 28,
'MIN_ZOOM': 1,
'SCALE':'both',
'TILES': [], }

Related

Django - Reset all filters

I am trying to clear all filters on button click at once. This is what I have on filters.py file and filters class:
class Filters(django_filters.FilterSet):
id = django_filters.NumberFilter(label=_("ID"))
name = django_filters.TextFilter(label=_("Name"))
And in base template:
<form id="filters-filters-form" action="javascript:;" onsubmit="onSubmit(this)" class="form form-inline main-filter">
{% bootstrap_form filter.form layout='inline' %}
<div>
<button class="btn ml-auto mr-2" onclick="resetFilters()">Clear all</button>
{% trans "Apply" as button_text %}
{% bootstrap_button button_text button_class="btn-primary" %}
</div>
</form>
resetFilters() function:
var resetFilters = function() {
let formId = document.getElementById('filters-form')
let formChildren = formId.childNodes;
Array.from(formChildren).forEach(formChild => {
formChild.val(null).trigger('change')
});
}
Is there any easy way to reset all filters?
P.S: I need to reset these filters without any id of form-control because it will be reusable base template
you can just add a button and add the URL of your form :
<a class="btn ml-auto mr-2" href={<pass your URL here>} > Clear All </a>

Django - second trigger of htmx tries to load an unexpected URL gives a 403 error

I have a table of cells, each with a different object instance, and using htmx to update the objects. I've created a CBV that takes the request.post from the htmx and saves the modified object to the db. The htmx does a hx-swap and loads a new <input> tag into my form, along with some background-color style based on the saved object. I can click on many cells and update several objects this way. This is working as expected and I don't see any errors.
However, the second time that I try to update the same cell/object, I get a 403 error. This error does not show in the browser. The cell just seems unresponsive. I can continue updating other cells. The error shows up in my browser console.
views
asessmentdetail - view that loads template and retrieves all the objects to fill in the table
def assessmentdetail(request, assess_pk, class_pk):
"""List the current grades for the assessment"""
user = request.user
assessment = Assessment.objects.get(id=assess_pk)
classblock = Classroom.objects.get(id=class_pk)
course_pk = classblock.course.pk
objective_list = Objective.objects.all().filter(
assessment=assess_pk).order_by('objective_name')
student_list = Student.objects.all().filter(
classroom=class_pk).order_by('nickname')
gra = Grade.objects.filter(
assessment=assess_pk).filter(cblock=classblock.id)
context = {'objective_list': objective_list}
new_or_update = gra.exists()
grade_report = [str(new_or_update)]
grade_list = []
grade_id_array = []
grade_report.append(len(objective_list))
# print(len(objective_list))
comment_list = []
n = 0
if gra:
for student in student_list:
# grade_report.append(student.nickname)
comms = AssessComment.objects.filter(
student=student, assessment=assessment).first()
if comms:
comment_list.append(comms)
else:
new_c = AssessComment(user=user,
student=student, assessment=assessment, comment="---")
new_c.save()
comment_list.append(new_c)
for obj in objective_list:
if gra.filter(objective=obj, student=student.id).last():
grade_report.append(q.score)
grade_id_array.append(q.id)
grade_list.append(q)
n = n + 1
context['grade_report'] = grade_report
context['grade_list'] = grade_list
context['grade_id_array'] = grade_id_array
context['student_list'] = student_list
context['assessment'] = assessment
context['class_pk'] = class_pk
context['assess_pk'] = assess_pk
context['course_pk'] = course_pk
context['comment_list'] = comment_list
context['classblock'] = classblock
)
return render(request, "gradebook/assessment_detail.html", context)
GradeChange - the view that modifies the object via htmx
class GradeChange(SingleObjectMixin, View):
""" view to handle htmx grade change"""
model = Grade
def post(self, request, *args, **kwargs):
grade = self.get_object()
ns = request.POST.get('score')
new_score = ns.upper()
def get_color(grade): # map background color to the score
if grade == "EXT":
convert_code = "rgba(153,102,255,0.4)"
elif grade == "APP+" or grade == "PRF+":
convert_code = "rgba(75, 192, 192, 0.7)"
elif grade == "APP" or grade == "PRF":
convert_code = "rgba(75, 192, 192, 0.3)"
elif grade == "DEV":
convert_code = "rgba(255, 205, 86, 0.4)"
elif grade == "EMG" or grade == "BEG":
convert_code = "rgba(225, 99, 132, 0.4)"
else:
convert_code = "rgba(0, 0, 0, 0.1)"
return (convert_code)
score_list = ["EXT", "APP+", "PRF+", "APP", "PRF", "DEV", "BEG", "EMG", "I", "---"]
if new_score in score_list:
grade.score = new_score
grade.save()
grade_score=str(grade.score)
bgcode = get_color(grade.score)
input_string=f'<input type="text" hx-post="{{% url "gradebook:grade-change" { grade.pk} %}}" hx-swap="outerHTML" hx-trigger="keyup delay:2s" class="form-control score" style="background-color:{ bgcode }" title="{ grade_score }" name="score" id="input-{{ forloop.counter0 }}" placeholder="{ grade.score }" required>'
else:
bgcode = get_color(grade.score)
input_string=f'<input type="text" hx-post="{{% url "gradebook:grade-change" { grade.pk} %}}" hx-swap="outerHTML" hx-trigger="keyup delay:2s" class="form-control score" style="background-color:{ bgcode }; border:solid rgb(255, 0, 0,.5);" title="xxx" name="score" id="input-{{ forloop.counter0 }}" placeholder="{ new_score }" required>'
return HttpResponse(input_string)
template
<div class="container ps-4">
<div class="row">
<div class="col-2">
<h5>{{ assessment.assessment_name }}</h5>
</div>
<div class="col-4">Class: {{ classblock }}, Assessment Date: {{ assessment.date_created|date:'Y-m-d' }}
</div>
<div class="col-2" id="edit">
<p>Click an item to edit.</p>
</div>
<div class="col-4">
<button type="submit" class="btn btn-secondary">Return to Assessment List</button>
</div>
<hr/>
</div>
{% if objective_list %}
<div class="row" id="create">
<div class="col-md-3">
<div>No grades entered yet.</div>
</div>
<div class="col-md-3">
<button class="btn btn-primary">Add Grades</button>
</div>
</div>
<div class="table-responsive" id = "grade-table">
<table class="table table-bordered table-sm">
<thead>
<tr>
<th class="col-3" scope="col">Students</th>
{% for obj in objective_list %}
<th class="col-2" scope="col">{{ obj.objective_name }}</th>
{% endfor %}
<th scope="col">Comments</th>
</tr>
</thead>
<tbody>
<form action="" method="post" class="form-group">
<div id="hxtarget">test</div>
{% for student in student_list %}
<tr>
<td >{{ student.student_first }} {{ student.student_last }}</td>
{% for g in grade_list %}
{% if g.student.id == student.id %}
<td>
<input type="text" hx-post="{% url 'gradebook:grade-change' g.pk %}" hx-swap="outerHTML" hx-trigger="keyup delay:2s" class="form-control score" title={{ g.score }} name="score" id="input-{{ forloop.counter0 }}" placeholder={{ g.score }} required>
</td>
{% endif %}
{% endfor %}
<td>
{% for comms in comment_list %}
{% if comms.student == student %}
<a class="grade-comment" href="{% url 'gradebook:addcomment' comms.pk assess_pk class_pk %}">{{ comms.comment|truncatewords:10 }}</a>
{% endif %}
{% endfor %}
</td>
</tr>
{% endfor %}
</form>
</tbody>
</table>
</div>
{% else %}
<p>No objectives. Please add an objective to this assignment.</p>
{% endif %}
</div>
urls
path('assessmentdetail/<uuid:assess_pk>/<uuid:class_pk>/',
views.assessmentdetail, name='assessdetail'),
path('grade-change/<uuid:pk>/', views.GradeChange.as_view(), name='grade-change'),
The error I get in the console is:
Request URL: http://127.0.0.1:8000/gradebook/assessmentdetail/3f4c7422-89d0-442c-a310-46aff8123949/be8ef548-25a7-4793-99d1-ff3cc4166921/%7B%%20url
Request Method: POST
Status Code: 404 Not Found```
The url for this page in my browser is http://127.0.0.1:8000/gradebook/assessmentdetail/3f4c7422-89d0-442c-a310-46aff8123949/be8ef548-25a7-4793-99d1-ff3cc4166921/. I don't know where the %7B%%20url comes from. I don't understand htmx well enough to know why it's calling this url.
The problem is that the response is not HTML but unrendered Django Template code, that's obviously not gonna work on the frontend. You must return pure HTML (or use a frontend template system, but it's not the topic of your question.)
The problematic variable is the input_string:
input_string=f'<input type="text" hx-post="{{% url "gradebook:grade-change" { grade.pk} %}}" hx-swap="outerHTML" hx-trigger="keyup delay:2s" class="form-control score" style="background-color:{ bgcode }" title="{ grade_score }" name="score" id="input-{{ forloop.counter0 }}" placeholder="{ grade.score }" required>'
HTMX expects that this is valid HTML, so the hx-post attribute will be just: hx-post="{{% url ". That's because the Django Template's url is not evaluated on the backend. So there's the origin of the strange %7B%%20url string in your logs, the %7B is the URL-encoded version of { bracket.
The fix is very easy, just use the reverse() method on the backend:
from django.urls import reverse
input_string=f'<input type="text" hx-post="{reverse("gradebook:grade-change", args=[grade.pk])}" hx-swap="outerHTML" hx-trigger="keyup delay:2s" class="form-control score" style="background-color:{ bgcode }" title="{ grade_score }" name="score" placeholder="{ grade.score }" required>'
Note: I have removed the id="input-{{ forloop.counter0 }}" because it seems unnecessary and requires passing the loop counter from the frontend. It's possible of course with e.g. hx-vals if you need it.

How to add comment on django site with ajax call and without refreshing?

On site exists form where users able to comment products. Comments are connected with products. jQuery is used from Bootstrap base.html (it's worked with another ajax-call). I'm fighting with ajax comment during the week). Trying to do it only for one product to understand how it works. Without ajax system of comments works fine, but I decided to add smooth in comment add without refreshing of the page and I do task with POST and ajax for the first time (before I did two pretty simple examples with get and form reset after successful response to ajax from back-end). Could somebody advice what should be add in template and view ? I suppose problem is connected with my poor knowledge of js.
I am using
is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
because this is working with django 4.0, and old function is_ajax() was removed.
views.py
def ajax_test(request):
product = Product.objects.get(id=4)
comments = product.comment_set.order_by('-created_at')
form = UserCommentForm
context = {'product':product,'comments':comments,'form': form}
return render(request, 'store/ajax_test.html', context)
def ajax_receiver(request):
is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
if request.method == 'POST' and is_ajax:
product = Product.objects.get(id=4)
form = UserCommentForm(data=request.POST)
user = Comment.objects.get(author=user)
if form.is_valid() and request.user.is_authenticated:
new_comment = form.save(commit=False)
new_comment.author = request.user
new_comment.product = product
new_comment.save()
comment_info = {
"author": new_comment.author,
"new_comment": new_comment.content,
"created_at": new_comment.created_at,
}
return JsonResponse({"comment_info": comment_info}, status=200)
else:
return JsonResponse({"success": False}, status=400)
template
{% extends "store/base.html" %}
{% load bootstrap4 %}
{% block page_header %}
<h1>тестирую аякс</h1>
{% endblock page_header %}
{% block content %}
<div class="container">
<div class="row py-3">
<div class="col-3 order-2" id="sticky-sidebar">
<div class="sticky-top">
<form id="UserCommentForm" method='post' class="form">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button name="submit" class="btn btn-primary" name="comment">Добавить комментарий</button>
{% endbuttons %}
</form>
</div>
</div>
<div class="col" id="main">
<h3>Комментарии:</h3>
{% for comment in comments %}
<h6 class="card-header">
{{ comment.author }}<small> добавлен {{ comment.created_at|date:'M d, Y H:i' }} </small>
</h6>
<div class="card-body">
<h4>{{ comment }}</h4>
<form action="" method="POST">
{% csrf_token %}
</form>
</div>
{% empty %}
<p>Для данного товара ещё нет комментариев.</p>
{% endfor %}
</div>
</div>
</div>
{% block javascript %}
<script type="text/javascript">
$(document).ready(function(){
$("#UserCommentForm").submit(function(e) {
// prevent from normal form behaviour
e.preventDefault();
// serialize the form data
var serializedData = $(this).serialize();
$.ajax({
type : 'POST',
url : "{% url 'store:ajax_receiver' %}",
data : serializedData,
success : function(response){
??? what should be there to post ???
},
error : function(response){
console.log(response)
}
});
});
});
</script>
{% endblock javascript %}
{% endblock content %}
Finally I found the decision, it's just to reload block of comments without refreshing, there is the code, the space before ' #main' is important!:
<script type="text/javascript">
$(document).ready(function(){
$("#UserCommentForm").submit(function(e){
// prevent from normal form behaviour
e.preventDefault();
// serialize the form data
var serializedData = $(this).serialize();
$.ajax({
type : 'POST',
url : "{% url 'store:ajax_receiver' %}",
data : serializedData,
success : function(response){
$('#main').load(' #main', function(){
/// can add another function here
});
$("#UserCommentForm")[0].reset();
},
error : function(response){
console.log(response)
}
});
});
});
</script>
In your ajax success write the following code to show new comment:
let comment_info = response["comment_info"];
let author = comment_info["author"];
let comment = comment_info["comment"];
let created_at = comment_info["created_at"];
html = "<h6 class='card-header'> "+author+"</h6>"
// i have writting only part of html write the full just like you do when you show comments
$("#main").append(html);

sweetalert not working on django template

I started with the following code to send the ID to a method to delete an object of model.But the first ID is always sent.
this is my HTML and JavaScript code:
{% for yad in yads %}
<form action="{% url 'del-yadd' %}" method="post" id="myform">
{% csrf_token %}
<button type="button" name="yidd" value="{{yad.id}}" id="btndelme" class=" btn btn-danger" onclick="
swal({
title: 'warning!',
text: 'do you want to delete?',
type: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
cancelButtonText:'no',
confirmButtonText: 'yes'
},
function(isConfirm) {
if (isConfirm) {
document.querySelector('#myform').submit();
}});
">delete</button>
</form>
{% endfor %}
and this is my Django view code:
def delete_yadd(request):
id=request.POST['yidd']
yad=Yadd.objects.filter(id=id).delete()
return redirect('home')
The reason why your SweetAlert is not showing is because it needs to be called right near the end of the html document by using a javascript function. Things I have changed are the way you call the sweetalert function like below:
{% for yad in yads %}
<form action="{% url 'del-yadd' %}" method="post" id="myform">
{% csrf_token %}
<button type="button" name="yidd" value="{{yad.id}}" id="btndelme" class=" btn btn-danger" onclick="confirm()"></button>
</form>
{% endfor %}
<script>
function confirm() {
swal({
title: 'warning!',
text: 'do you want to delete?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
cancelButtonText: 'no',
confirmButtonText: 'yes'
},
function (isConfirm) {
if (isConfirm) {
document.querySelector('#myform').submit();
}
})
}
</script>

Why page is getting loaded even I use ajax?

def tracker(request):
if request.method == "POST":
orderId = request.POST.get('orderId','')
email = request.POST.get('email','')
try:
order = Orders.objects.filter(order_id=orderId, email=email)
if len(order) > 0:
update = Updateorder.objects.filter(order_id=orderId)
updates = []
for item in update:
updates.append({'text':item.update_desc, 'time':item.time_stamp})
response = json.dumps(updates, default=str)
return HttpResponse(response)
else:
print(len(order))
return HttpResponse(f"{len(order)}")
except Exception as e:
return HttpResponse(f'exception {e}')
return render(request,'shop/Tracker.html')
{% extends 'shop/base.html' %} {% block title %}MAC Tracker {% endblock %} {% block body %}
<div class="container">
<div class="col-md-8 offset-md-2 my-4">
<h3>Step 1 - Enter your Email address and tracker ID to track your order</h3>
<form method="post" id="trackerForm" action="#">{% csrf_token %}
<div class="form-row">
<div class="form-group col-md-6">
<label for="name">Tracker ID</label>
<input type="text" class="form-control" id="orderId" name="orderId" placeholder="Enter tracker ID">
</div>
<div class="form-group col-md-6">
<label for="email">Email</label>
<input type="email" class="form-control" id="email" name="email" placeholder="Enter Email">
</div>
</div>
<button type="submit" class="btn btn-primary">Track Order</button>
</form>
</div>
<div class="col-md-8 offset-md-2 my-5">
<h3>Your order status</h3>
<ul class="list-group" id="items">
<b>Enter your order Id and Email and click Track Order to find details about your order!</b>
</ul>
</div>
</div>
{% endblock %} {% block js %}
<script>
$('#trackerForm').submit(function(event) {
$('#items').empty();
var formData = {
'orderId': $('input[name=orderId]').val(),
'email': $('input[name=email]').val(),
'csrfmiddlewaretoken': $('input[name=csrfmiddlewaretoken]').val()
};
$.ajax({
type: 'POST',
url: '/shop/tracker/',
data: formData,
encode: true
})
.done(function(data) {
console.log(data);
updates = JSON.parse(data);
if (updates.length > 0 & updates != {}) {
for (i = 0; i < updates.length; i++) {
let text = updates[i]['text'];
let time = updates[i]['time'];
mystr = `<li class="list-group-item d-flex justify-content-between align-items-center">
${text}
<span class="badge badge-primary badge-pill">${time}</span>
</li>`
$('#items').append(mystr);
}
} else {
mystr = `<li class="list-group-item d-flex justify-content-between align-items-center">
Sorry, We are not able to fetch this order id and email. Make sure to type correct order Id and email</li>`
$('#items').append(mystr);
}
});
event.preventDefault();
});
</script>
{% endblock %}
I am using ajax to submit the form when I try to submit the form it is showing the error jquery-3.4.1.js:9837 POST http://127.0.0.1:8000/shop/tracker/ 404 (Not Found).
I have added event.preventDefault(); still it is not working
$.ajax({
type: 'POST',
url: '/shop/tracker/',
data: formData,
encode: true
})
.done(function(data) {
console.log(data)
updates = JSON.parse(data);
if (updates.length > 0 & updates != {}) {
for (i = 0; i < updates.length; i++) {
let text = updates[i]['text'];
let time = updates[i]['time'];
mystr = <li class="list-group-item d-flex justify-content-between align-items-center">
${text}
<span class="badge badge-primary badge-pill">${time}</span>
</li>
$('#items').append(mystr);
I am expecting the html output in the same page but there is no changes occurring.for reference views.py and html page has shown in above sections.
thank you stefan and henrik for your comments I just corrected the url and the page is working properly. The corrected url : '/shop/tracker' earlier it was '/shop/tracker/' one extra slash at the end causing the problem.