Javascript - Editing Form with Fetch - javascript

I'm trying to edit an existing form on my site, and save the edits using Javascript (without requiring a refresh of the page). I'm using Django as well.
So far, when the user clicks 'edit' on the page, the form appropriately appears, showing the information already saved there. But when I click 'save' I get a 404 error.
The issue is in the Javascript function edit_post. I'm not sure if I have used stringify correctly either, I'm new to using Javascript with Django. Any help is appreciated.
function edit_handeler(element) {
id = element.getAttribute("data-id");
document.querySelector(`#post-edit-${id}`).style.display = "block";
document.querySelector(`#post-content-${id}`).style.display = "none";
// everything above this works and opens up the form for editing
edit_btn = document.querySelector(`#edit-btn-${id}`);
edit_btn.textContent = "Save";
edit_btn.setAttribute("class", "text-success edit");
if (edit_btn.textContent == "Save") {
edit_post(id, document.querySelector(`#post-edit-${id}`).value); //here
edit_btn.textContent = "Edit";
edit_btn.setAttribute("class", "text-primary edit");
}}
function edit_post(id, post) {
const body = document.querySelector(`#post-content-${id}`).value;
fetch("/edit_post/", {
method: "POST",
body: JSON.stringify({
body:body
})
}).then((res) => {
document.querySelector(`#post-content-${id}`).textContent = post;
document.querySelector(`#post-content-${id}`).style.display = "block";
document.querySelector(`#post-edit-${id}`).style.display = "none";
document.querySelector(`#post-edit-${id}`).value = post.trim();
});
}
Relevant html - this is inside a card, for the post itself in the html file:
<span id="post-content-{{i.id}}" class="post">{{i.text}}</span> <br>
<textarea data-id="{{i.id}}" id="post-edit-{{i.id}}"
style="display:none;" class="form-control textarea" row="3">{{i.text}}</textarea>
<button class="btn-btn primary" data-id="{{i.id}}" id="edit-btn-{{i.id}}"
onclick="edit_handeler(this)" >Edit</button> <br><br>
views.py
def edit_post(request, pk):
post = Post.objects.get(id=pk)
form = PostForm(instance=post)
if request.method == "POST":
form = PostForm(request.POST, instance=post)
if form.is_valid():
form.save()
return JsonResponse({}, status=201) # this works to edit and save to db
else:
if request.method == "GET":
form = PostForm(instance=post)
form_for_post = {'form': PostForm()}
return render(request, "network/make_post.html", {
"post": post,
"form_for_post": form,
})
urls.py (relevant ones)
path('edit_post/<str:pk>/', views.edit_post, name="edit_post"),
path('edit_post/', views.edit_post),
path("profile/<str:username>", views.profile, name="profile"),

Assuming you're running your django server locally and you're getting a 404 returned from your fetch request, then that means the url path does not exist. Either your django server isn't live or the url you're supplying the fetch request with is incorrect. If /edit_post/ is your desired endpoint, try fetching with the servers full request URL
Something like this
http://localhost:8000/edit_post
Replace 8000 with whatever port your server is running at.
Your fetch body is structured properly btw.

Related

Django Admin Page execute javascript on Custom Action Buttons

I'm trying to add a custom button in each row next to a user to the django admin page which makes it easier for the admin to click the button and generate a password reset link for a particular user rather than navigate to the hidden actions section of the django admin page.
Something like this - https://hakibenita.com/how-to-add-custom-action-buttons-to-django-admin
Following is the code I've implemented and it works and navigates to a new page with the correct reset-password request and the reset_password() method executes with a link being sent to the user.
However, when I click the button, I would like to send an AJAX GET request to the same url as above and just a show an alert to the admin on request completion instead of navigating to a new page. Currently I'm unable to run the javascript code using the format_html method in Django (see code below)
class UserAdmin(Admin.modelAdmin):
list_display = ('name', 'email', 'custom_actions')
form = forms.UserAdminForm
def get_urls(self):
urls = super().get_urls()
custom_urls = [
url(
r'reset-password/(?P<user_id>.+)',
self.admin_site.admin_view(self.reset-password),
name='reset-password',
),
]
return custom_urls + urls
def custom_actions(self, obj):
user = user_model.User.get(user_id=obj.id)
password_reset_url = reverse('admin:reset-password', args=[user.id])
return mark_safe(format_html(
f'<a class="button" onclick="parent.location=\'{password_reset_url}\'" >Reset Password</a> '
custom_actions.short_description = 'Custom Actions'
custom_actions.allow_tags = True
def reset_password(self, request, password_reset_id):
password_reset.send_password_reset_link(password_reset_id=password_reset_id)
return HttpResponse(status=200)
Below is the HTML/JS code that I was testing for the behavior I want on the page and works, I was hoping to the stitch the same into the above Django code but Django cannot execute and just shows the script as a string on the admin page.
<button id='jax' class="button">AJAX</button>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
$(document).ready(function() {
$("#jax").click(function(){
$.ajax({
type : "GET",
url : "my-dynamic-url",
timeout:10,
jsonp : "jsonp",
success : function (response, textS, xhr) {
console.log('oof')
alert("Password reset link has been resent");
},
error : function (xmlHttpRequest, textStatus, errorThrown) {
if(textStatus==='timeout')
alert("request timed out");
}
});
});
});
</script>
Is there a correct way to integrate the above javascript code on admin page on the click of the button?
Are you missing out on commas? From an example according to the documentation:
format_html("{} <b>{}</b> {}",
mark_safe(some_html),
some_text,
some_other_text,
)
Maybe you should wrap {password_reset_url} in commas and see if that works.
If this does not solve your problem, elaborating on this will be helpful:
Django cannot execute and just shows the script as a string on the admin page.
Which script are you referring to?
The solution:
Python:
class UserAdmin(Admin.modelAdmin):
list_display = ('name', 'email', 'custom_actions')
form = forms.UserAdminForm
def get_urls(self):
urls = super().get_urls()
custom_urls = [
url(
r'reset-password/(?P<user_id>.+)',
self.admin_site.admin_view(self.reset-password),
name='reset-password',
),
]
return custom_urls + urls
def custom_actions(self, obj):
user = user_model.User.get(user_id=obj.id)
password_reset_url = reverse('admin:reset-password', args=[user.id])
return mark_safe(format_html(
f'<a class="button" onclick="send_password_link(\'{password_reset_url}\'") >Reset Password</a> '
custom_actions.short_description = 'Custom Actions'
custom_actions.allow_tags = True
def reset_password(self, request, password_reset_id):
password_reset.send_password_reset_link(password_reset_id=password_reset_id)
return HttpResponse(status=200)
HTML:
This should be under base-app/templates/your-app/change_list.html
{% extends "admin/change_list.html" %}
{% load static %}
{% block content %}
<script src="{% static 'js/passwordReset.js' %}"></script>
<!-- Render the rest of the ChangeList view by calling block.super -->
{{ block.super }}
{% endblock %}
Javascript:
This should be under base-app/static/static/js/passwordReset.js
function send_password_link(url) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
alert('Password Reset Sent');
}
};
xhttp.open("GET", url, true);
xhttp.send();
}

Redirect to same page after form submitted in views.py not working while using Django paginator

I want to redirect to same page after submitting form which fetches view from views.py. The problem is paginator. After submitting Django form page reloads to page 1. I want browser to stay on the same page after submitting form. Error I get: django.urls.exceptions.NoReverseMatch: 'http' is not a registered namespace. Help would be greatly appreciated!
Code calling path in js:
let path = window.location.href
Fetching api:
subedit[i].addEventListener('click', () => {
fetch(`/edit/${content[i].id}`,
{
method: 'POST',
headers: {'X-CSRFToken': csrftoken},
mode: 'same-origin',
body: JSON.stringify({
post: textarea[i].value,
page: path
})}).then(() => {
editdiv[i].style.display = 'none';
post[i].style.display = 'block';
})})
views.py:
def edit(request, post_id):
data = json.loads(request.body)
content = data.get("post", "")
post=Post.objects.get(id=post_id)
page = data.get("page", "")
if request.method == "POST":
if post.user == request.user:
post.post=content
post.save()
return HttpResponseRedirect(reverse(str(page)))
reverse(…) [Django-doc] looks for a view with the given name, it does not take a URL as input. You can use this directly in the HttpResponseRedirect [Django-doc], so:
def edit(request, post_id):
data = json.loads(request.body)
content = data.get("post", "")
post=Post.objects.get(id=post_id)
page = data.get("page", "")
if request.method == 'POST':
if post.user == request.user:
post.post=content
post.save()
return HttpResponseRedirect(page)
You should also return HttpResponses for the GET requests, and in case the post.user is not the same as the request.user.
Note: It is often better to use get_object_or_404(…) [Django-doc],
then to use .get(…) [Django-doc] directly. In case the object does not exists,
for example because the user altered the URL themselves, the get_object_or_404(…) will result in returning a HTTP 404 Not Found response, whereas using
.get(…) will result in a HTTP 500 Server Error.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
I was able to get it working using sessions:
<a id="page" class="page-link">{{ num }}</a>
let page=document.querySelector("#page").innerHTML
fetch(`/edit/${content[i].id}`,
{
method: 'POST',
headers: {'X-CSRFToken': csrftoken},
mode: 'same-origin',
body: JSON.stringify({
post: textarea[i].value,
page: page
})}).then(() => {
editdiv[i].style.display = 'none';
post[i].style.display = 'block';
})})
def edit(request, post_id):
data = json.loads(request.body)
content = data.get("post", "")
post=Post.objects.get(id=post_id)
page = data.get("page", "")
request.session['page'] = page
if request.method == "POST":
if post.user == request.user:
post.post=content
post.save()
return HttpResponse(page)
def index(request):
posts = Post.objects.all().order_by('-date')
paginator = Paginator(posts,10)
page_number = request.GET.get('page')
if request.session.has_key('page'):
page_number = request.session['page']
del request.session['page']

Cannot Edit Form in Javascript With Django

I'm trying to edit a form on this site with Django, and save the edits using Javascript (but not mandating a page refresh).
When the user clicks 'edit' on the page, nothing happens. No console errors. I think the issue is in the Javascript function edit_post. I'm new to using Javascript with Django. Any help is appreciated.
Relevant urls.py
path('edit_post/<str:id>', views.edit_post, name="edit_post"), #before was pk not id
path('edit_post/', views.edit_post),
path("profile/<str:username>", views.profile, name="profile"),
Javascript
function edit_handeler(element) {
id = element.getAttribute("data-id");
document.querySelector(`#post-edit-${id}`).style.display = "block";
document.querySelector(`#post-content-${id}`).style.display = "none";
// everything above this works and opens up the form for editing
edit_btn = document.querySelector(`#edit-btn-${id}`);
edit_btn.textContent = "Save";
edit_btn.setAttribute("class", "text-success edit");
if (edit_btn.textContent == "Save") {
edit_post(id, document.querySelector(`#post-edit-${id}`).value); //here
edit_btn.textContent = "Edit";
edit_btn.setAttribute("class", "text-primary edit");
}}
function edit_post(id, post) {
const body = document.querySelector(`#post-content-${id}`).value;
fetch(`/edit_post/${id}`, {
method: "POST",
body: JSON.stringify({
body:body
})
}).then((res) => {
document.querySelector(`#post-content-${id}`).textContent = post;
document.querySelector(`#post-content-${id}`).style.display = "block";
document.querySelector(`#post-edit-${id}`).style.display = "none";
document.querySelector(`#post-edit-${id}`).value = post.trim();
});
}
Relevant html
<span id="post-content-{{i.id}}" class="post">{{i.text}}</span> <br>
<textarea data-id="{{i.id}}" id="post-edit-{{i.id}}"
style="display:none;" class="form-control textarea" row="3">{{i.text}}</textarea>
<button class="btn-btn primary" data-id="{{i.id}}" id="edit-btn-{{i.id}}"
onclick="edit_handeler(this)" >Edit</button> <br><br>
views.py
def edit_post(request, id):
post = Post.objects.get(id=id)
form = PostForm(instance=post)
if request.method == "POST":
form = PostForm(request.POST, instance=post)
if form.is_valid():
form.save()
return JsonResponse({}, status=201)
else:
if request.method == "GET":
form = PostForm(instance=post)
form_for_post = {'form': PostForm()}
return render(request, "network/make_post.html", {
"post": post,
"form_for_post": form,
})

How to download a document generated with python-docx in Django project?

I'm trying to figure how to download a word document generated with python-docx in my django app (I'm still learning and this is the first time I working with documents); with the help of ajax I send all the information needed to the view and call a function that uses that information and returns the document, then I'm trying to send this document as response in order to download it with the help of a "Download" button (or show web browser download dialog) in the same template from where I'm submitting the data, but here is where I'm stuck.
to send this document as response in order to download it with the help of a "Download" button (or show web browser download dialog) in the same template from where I'm submitting the data, but here is where I'm stuck.
What I have until now is:
1) In javascript I'm sending the information as follows:
data = {
categoria: cat,
familia: fam,
Gcas: gcas,
FI: FI,
FF: FF,
Test: test,
Grafica: grafica
},
$.ajax({
type: 'post',
headers: {
"X-CSRFToken": csrftoken
},
url: url,
data: { json_data: JSON.stringify(data) },
success: function (response) {
$('#instrucciones').hide(); //Hide a div with a message
$('#btndesc').show(); //Show the button to download the file generated
}
});
return false;
}
2) In my Django view:
def Documento(request):
if request.method == "GET":
context={}
context['form'] = catForm
return render(request, 'report/report_base.html', context)
if request.method == 'POST':
#Data from ajax
datos = request.POST.get('json_data')
jsondata = json.loads(datos)
Gcas = jsondata['Gcas']
FI = jsondata['FI']
FF = jsondata['FF']
grafica = jsondata['Grafica']
#Using function to create the report
Reporte = ReporteWord(Gcas, FI, FF, grafica)
#Response
response = HttpResponse(content_type='application/vnd.openxmlformats-
officedocument.wordprocessingml.document')
response['Content-Disposition'] = 'attachment; filename = "Reporte.docx"'
response['Content-Encoding'] = 'UTF-8'
Reporte.save(response)
return response
3) My function to create the document looks like:
def ReporteWord( gcas, FI, FF, Chart):
#Cargamos el template
template = finders.find('otros/Template_reporte.docx')
document = Document(template)
#Header
logo = finders.find('otros/logo.png')
header = document.sections[0].header
paragraph = header.paragraphs[0]
r = paragraph.add_run()
r.add_picture(logo)
#Adding title
titulo = document.add_heading('', 0)
titulo.add_run('Mi reporte').bold = True
titulo.style.font.size=Pt(13)
.
Many other steps to add more content
.
.
#IF I SAVE THE FILE NORMALLY ALL WORKS FINE
#document.save(r'C:\tests\new_demo.docx')
return document
I'll be very grateful for any idea or suggestion, many thanks in advance.
NOTE: I've reviewed these answers (and others) without luck.
Q1, Q2, Q3, Q4
UPDATE: Thanks to the feedback received I finally found how to generate the document and show the download dialog:
As was suggested the best way to achieve its using the view and not ajax, so the final updates in the code are:
a) Update view to work as show in feedback
b) JavaScript - Ajax control for POST method was removed and now all is handled directly with python (no extra code needed)
1) View:
def Reporte(request):
if request.method == "GET":
context={}
context['form'] = catForm
return render(request, 'reportes/reporte_base.html', context)
if request.method == 'POST':
#Getting data needed after submit the form in the page
GcasID = request.POST.get('GCASS')
FI = request.POST.get('dp1')
FF = request.POST.get('dp2')
Grafica = request.POST.get('options')
#Function to obtain complete code from GcasID
Gcas = GcasNumber(GcasID)
#Report creation
Reporte = ReporteWord(Gcas, FI, FF, Grafica)
#PART UPDATED TO SHOW DOWNLOAD REPORT DIALOG
bio = io.BytesIO()
Reporte.save(bio) # save to memory stream
bio.seek(0) # rewind the stream
response = HttpResponse(
bio.getvalue(), # use the stream's contents
content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
)
response["Content-Disposition"] = 'attachment; filename = "Reporte.docx"'
response["Content-Encoding"] = "UTF-8"
return response
With those changes now when I press "Create report" (submit button of form) all works as expected (as a plus no more libraries are necessary). At the end as you suggested its easier do it in this way than using ajax.
Many thanks to all for your kind help.
Python-docx's Document.save() method accepts a stream instead of a filename. Thus, you can initialize an io.BytesIO() object to save the document into, then dump that to the user.
Reporte = ReporteWord(Gcas, FI, FF, grafica)
bio = io.BytesIO()
Reporte.save(bio) # save to memory stream
bio.seek(0) # rewind the stream
response = HttpResponse(
bio.getvalue(), # use the stream's contents
content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
)
response["Content-Disposition"] = 'attachment; filename = "Reporte.docx"'
response["Content-Encoding"] = "UTF-8"
return response
This will work if you use a regular link or a form to submit the request, but since you're using $.ajax, you may need to do additional work on the browser end to have the client download the file. It would be easier not to use $.ajax.
Yep, a cleaner options, as stated by wardk would be, using https://python-docx.readthedocs.org/:
from docx import Document
from django.http import HttpResponse
def download_docx(request):
document = Document()
document.add_heading('Document Title', 0)
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document')
response['Content-Disposition'] = 'attachment; filename=download.docx'
document.save(response)
return response
Know more

Linking javascript variable to Flask

This is my index.html file
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script type="text/javascript">
// setup some JSON to use
var cars = [
{ "make":"Porsche", "model":"911S" },
{ "make":"Mercedes-Benz", "model":"220SE" },
{ "make":"Jaguar","model": "Mark VII" }
];
window.onload = function() {
// setup the button click
document.getElementById("theButton").onclick = function() {
doWork()
};
}
function doWork() {
// ajax the JSON to the server
$.post("result", JSON.stringify(cars), function(){
});
// stop link reloading the page
event.preventDefault();
}
</script>
This will send data using AJAX to Python:<br /><br />
<form action = "/result" method = "POST">
<button id = "theButton" class ="button">
<span>
<i >Click</i>
</span>
</button>
<form>
This is my json_io.py file to run Flask:
#!flask/bin/python
import sys
from flask import Flask, render_template, request, redirect, Response
import random, json
app = Flask(__name__)
#app.route('/')
def output():
# serve index template
return render_template('index.html')
#app.route('/result', methods = ['POST', "GET"])
def worker():
# read json + reply
data = request.get_json(force = True)
print(data)
return render_template('result.html', result = data[0]["make"])
if __name__ == '__main__':
# run!
HOST = '127.0.0.1'
PORT = 4100
app.run(HOST, PORT, debug = True)
After running the command line and click on click button. I got what I want in the Chrome console.
In order to get into http://127.0.0.1:4100/result , I will comment event.preventDefault(); in index.html. However, when I rerun again, it shows me Bad Request Failed to decode JSON object: Expecting value: line 1 column 1 (char 0)
Are there any ideas on how I can fix this ?
In the index.html file, make a placeholder that will be filled out by the js code handling your AJAX request:
<span id='ajax_result'>placeholder</span>
Then, in the python code, you don't really need to go through the template and can return a string straight away:
#app.route('/result', methods = ['POST', "GET"])
def worker():
data = request.get_json(force = True)
return data[0]['make']
Then, in js, grab the result and put it in the placeholder span:
function doWork() {
$.post("result", JSON.stringify(cars), function(reply){
$('#ajax_result').text(reply);
});
event.preventDefault();
}
Click the button and enjoy your Porsche!

Categories