I'm new to Django and Python so bear with me.
I'm trying to submit a form (in test.html page) And once the form is submitted I want to load a new page(test.html/web1.html) and then the page will display continuous updates on tasks running.
This currently works on a single page, where I submit the form I get the updates on the same page using AJAX. I want to implement this on a new page. Below is my code.
test.js
$('#Submit_button').on('submit',function(event){
event.preventDefault();
console.log("form submitted!")
call_func();
});
function call_func() {
$.ajax({
url : "start_task/", // the endpoint
data:{...,'init':call_func.i}, // call_func.i to iterate 2 times
headers: {Accept: "application/json"},
success : function(json) {
if(call_func.i==0){
call_func.i=1;
call_func();
}
else ...
views.py
def start_task(request):
if request.method == 'POST':
init = request.POST.get('init')
print('init =',init)
if init==0:
return render(request, 'my_app/web1.html')
elif init==1:
# work on updates
return HttpResponse(
json.dumps(response_data),
content_type="application/json"
)
html
<form action="/test.html/start_task/" method="POST" id="Submit_button">
How do I use AJAX only for the latter part (to update the new page) and just load the page first after submit?
I think you need something like HttpResponseRedirect instead of HttpResponse:
if form.is_valid(): # validation
# Process your data
# ...
return HttpResponseRedirect('/new_page_url/')
Related
I am using Rails ruby 6.1.4 and ruby 2.6.7
I have a form partial that is used for both the new and edit views. There are two select drop-down form elements. The application.js code makes an Ajax call to the controller to get items to populate the 2nd drop-down based on what is selected in the 1st.
For the new view, my code works fine. But, when viewing a record in the edit view, it does not work. It seems to need an id in the path when on the edit view.
When using developer tools in my browser, the console window shows this error when on the edit view:
[XHR] GET http://localhost:3000/fitness/weights/24/exercises?muscle_group=SHOULDERS
As you can see, it wants the id in the path. But I do not need the id to get the exercises for the drop-down. And of course, the 2nd drop-down does not populate.
How can I adjust my route so both the new and edit views work correctly? OR, do I need to change my ajax call? NOTE: when I move the :exercises out of the collection in the routes, then the reverse happens; the new form does not work but the edit form does.
Here is my code:
application.js:
// if edit view is showing
// get the current value of the Muscle Group field
//
var current_muscle_group;
if( $('#fitness_weight_muscle_group') ){
current_muscle_group = $('#fitness_weight_muscle_group').val();
}
if( current_muscle_group != '' && current_muscle_group != 'Select One' && current_muscle_group != null ){
get_exercises(current_muscle_group);
}
// user selects muscle_group from drop-down
// new or edit form
//
$('#fitness_weight_muscle_group').change(function(){
get_exercises($(this).val());
})
// get_exercises
// ajax call to controller#exercises
// to get exercies for the muscle_group
//
function get_exercises(current_muscle_group){
$.ajax({
url: "exercises",
dataType: "json",
data: {muscle_group: current_muscle_group},
success: function(data){
populate_exercise_select(data);
}
});
}
...
Controller
...fitness/weights/controller:
protect_from_forgery except: [:exercises, :past_exercise, :max_weight]
...
def exercises
# run sql to get exercises for muscle group passed in
if params[:muscle_group]
#exercises=Fitness::Weight.select("DISTINCT exercise")
.where(admin_user_id: session[:user_id])
.where(muscle_group: params[:muscle_group].upcase)
return render json: #exercises
end
...
My routes
config/routes:
...
resources :weights do
collection do
get :exercises
...
end
## added this route to satisfy issue with ajax call
## ... to controller action that requires a record id
get :get_edit_exercises
end
...
Solution
I added a new route (see above) to solve the ID in the path issue when viewing the edit form for a record. I added a controller#get_edit_exercises action to match the route. It returns #exercises just like the def exercises does.
I changed application.js to call the new controller#get_edit_exercises action when it was an edit view. If an ID is in the path/url, then it is an edit view.
application.js
// get the exercises
function get_exercises(current_muscle_group){
var url = "exercises";
var current_record_id = get_current_record_id(); // function below
if( current_record_id > 0 ){
// get_exercises is for edit form. it expects an ID
url = "/fitness/weights/" + current_record_id + "/get_edit_exercises";
}
$.ajax({
url: url,
dataType: "json",
data: {muscle_group: current_muscle_group},
success: function(data){
populate_exercise_select(data);
}
})
}
function get_current_record_id() {
var pathname = $(location).attr('pathname');
return pathname.replace(/[^0-9]/g,'');
}
Note: the id was showing twice in the path, but using a full path in url: url solved that. The forward / was also needed.
url = "/fitness/weights/" + current_record_id + "/get_edit_exercises";```
I also added the new ```controller#get_edit_exercises``` to the controller's ```protect_from_forgery except: [...]```
Everything works. I just need to DRY up some code now.
In words, we have a muscle_group that has_many exercises, (and perhaps an exercise has_many muscle_groups), if it's a bidirectional has_many, then it's achieved with muscle_group has_and_belongs_to_many exercises and vice-versa.
So to populate the exercises drop-down, for a given muscle_group, I would suggest an ExerciseController with an index method that can accept a muscle_group id parameter and respond to an ajax request with a list of exercises.
So the routes would be:
resources :muscle_groups do
resources :exercises
end
and the controller:
class ExercisesController < ApplicationController
def index
#exercises = Exercise.joins(exercise_muscles: :muscles)
.where(muscles: { id: params[:muscle_group_id] })
render #exercises # assumes a partial file '_exercise.html.erb' produces the option tag for an exercise dropdown
end
end
Here's the thing: I thought I could receive an HTTP response and manage it with javascript ajax and do whatever I wanted with that response without the user even noticing. For example using console.log(response).
But it's not working and the response is showing in text form like an html.
I'll explain what I'm doing and my problem:
I'm making a comment section for a django app. My comment system was working fine. The user could post comments and see other peoples comments.
The thing is that I had to refresh the page every time to load the recently published comments.
That's not very cool, I want to be able to post a comment and see it immediately without reloading the page.
I did my research and found this comment on quora
You use AJAX and a Django backend view.
Something (a user leaving a new comment, a timer etc.) will trigger a
JavaScript AJAX call to a Django view requesting comments for the
page. That Django view will return the result to the JavaScript AJAX
call. It can be done in two ways:
it can just return the data, typically in JSON and then JavaScript
worries about rendering that data on the page
or, the view can run the
data through a Django template and return the partial HTML and then
the JavaScript just needs to swap out the HTML response chunk in the
content area for the comments (typically identified by a HTML ID,
where you replace the HTML content of the div with a certain ID with
the HTML you received from the view both approaches have pros and cons
and which is better depends on many factors
I am trying to follow the second way.
My problem is that the HttpResponse changes my entire page and displays what's in the response as html text!
Here's my views.py. This is the view that renders the page where the comment section is at
(I'm not sending the rendered html yet because I'm having problems with HTTP response)
def make_bid(request, pk):
listing = Listing.objects.get(id = pk)
comments = listing.comments.all()
if request.method == "POST":
if request.user.is_authenticated:
comment = Comment(
user = request.user,
comment= request.POST['comment'],
date = datetime.datetime.now().date(),
listing = listing)
comment.save()
context = {'comments': listing.comments.all()}
rendered = render_to_string("auctions/comments.html", context)
return HttpResponse("this a http response")
else:
return HttpResponse("user is not even authenticated")
else:
return render(request, "auctions/make-bid.html", {
'article' : listing,
'comments': comments
})
The html for the comment section
<aside class="comment__section">
<h3>Comments</h3>
<form action="{% url 'make-bid' article.id %}" method="post" id="form-comment">
{% csrf_token%}
<textarea id="textbox" class="field form__field--block" value="" name="comment" placeholder="What do you think about this listing?"></textarea>
<input class="button button__primary" type="submit" value="Publish">
</form>
<section class="comment__container">
<!-- comments from the http response should be inserted here with javascript-->
</section>
</aside>
The javascript. I copied this from another stackoverflow question. I was doing it myself with plain javascript and thought that might be the problem. It's not. It still doesn't give me the result I want
<script>
aside = document.querySelector(".comment__section")
// this is the id of the form
$("#from-comment").submit(function(e) {
e.preventDefault(); // avoid to execute the actual submit of the form.
var form = $(this);
var url = form.attr('action');
$.ajax({
type: "POST",
url: url,
data: form.serialize(), // serializes the form's elements.
success: function(data)
{
alert(data); // show response from the php script.
console.log('Submission was successful.');
console.log(data);
},
error: function(data){
console.log("no response")
}
});
});
</script>
I already tried using return JsonResponse. And still the same result :(
I am really lost here. I'm very new to Django and server-side stuff. And I have no idea why is the response showing like a whole page?
What am i missing?
Do http responses actually work differently as I think they do?
I would really appreciate any help. Thank you!!!
You have to serialize the data. Means, to convert django queryset into json format to send it to ajax.
from django.core import serializers
def make_bid(request, pk):
listing = Listing.objects.get(id = pk)
comments = listing.comments.all()
if request.method == "POST":
if request.user.is_authenticated:
comment = Comment(
user = request.user,
comment= request.POST['comment'],
date = datetime.datetime.now().date(),
listing = listing)
comment.save()
comments = listing.comments.all()
serialized_qs = serializers.serialize('json', list(comments))
data = {"queryset" : serialized_qs}
return JsonResponse(data)
else:
return HttpResponse("user is not even authenticated")
else:
return render(request, "auctions/make-bid.html", {
'article' : listing,
'comments': comments
})
I fixed it! My main problem was me. I made a few stupid mistakes. I had a typo and jquery wasn't loading. Here's how the script looks now:
<script src="{%static 'auctions/scripts/jquery-3.6.0.js' %}"></script>
<script>
$(document).ready(function(){
$("#form-comment").submit(function(e) {
e.preventDefault(); // avoid to execute the actual submit of the form.
var form = $(this);
var url = form.attr('action');
$.ajax({
type: "POST",
url: url,
data: form.serialize(), // serializes the form's elements.
success: function(data)
{
$("#comments").html(data);
$("#textbox").val('')
},
error: function(data){
console.log("no response")
}
});
});
})
</script>
And views.py. Now I'm sending the rendered comment section with an HttpResponse
def make_bid(request, pk):
listing = Listing.objects.get(id = pk)
context = {'comments': listing.comments.all()}
rendered = render_to_string("auctions/comments.html", context)
if request.method == "POST":
if request.user.is_authenticated:
comment = Comment(
user = request.user,
comment= request.POST['comment'],
date = datetime.datetime.now().date(),
listing = listing)
comment.save()
context = {'comments': listing.comments.all()}
rendered = render_to_string("auctions/comments.html", context)
return HttpResponse(rendered)
else:
return HttpResponse("user is not even authenticated")
else:
return render(request, "auctions/make-bid.html", {
'article' : listing,
'comments': rendered
})
And html looks like this
<aside class="comment__section" >
<h3>Comments</h3>
<form method="post" id="form-comment">
{% csrf_token%}
<textarea id="textbox" class="field form__field--block" value="" name="comment" placeholder="What do you think about this listing?"></textarea>
<input class="button button__primary" type="submit" value="Publish">
</form>
<section class="comment__container" id="comments">
{{comments}}
</section>
</aside>
I would still love to see a way to make the ajax call and handle the response with javascript. But I'm done with this comments section for now!
Thanks to everybody who answered this question!
I have managed to send & receive my JSON object in my views.py with a POST request (AJAX), but am unable to return render(request, "pizza/confirmation.html"). I don't want to stay on the same page but rather have my server, do some backend logic on the database and then render a different template confirming that, but I don't see any other way other than AJAX to send across a (large) JSON object. Here is my view:
#login_required
def basket(request):
if request.method == "POST":
selection = json.dumps(request.POST)
print(f"Selection is", selection) # selection comes out OK
context = {"test": "TO DO"}
return render(request, "pizza/confirmation.html", context) # not working
I have tried checking for request.is_ajax() and also tried render_to_string of my html page, but it all looks like my mistake is elsewhere. Also I see in my terminal, that after my POST request, a GET request to my /basket url is called - don't understand why.
Here is my JavaScript snippet:
var jsonStr = JSON.stringify(obj); //obj contains my data
const r = new XMLHttpRequest();
r.open('POST', '/basket');
const data = new FormData();
data.append('selection', jsonStr);
r.onload = () => {
// don't really want any callback and it seems I can only use GET here anyway
};
r.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
r.send(data);
return false;
In your basket view function, you always render the template as response. You can pass the parameters to your js snippet via HttpResponse. After request completed and you have response in your js function, you can use window.location.href to redirect the page you want. You can also look this answer to get more information.
I am deploying a website using Django. There is an application called 'forum', supporting a discussion forum on my website.
The url of the discussion forum is 'XXX.com/forum/roomY'. I want to refresh a div id = ''chat'', which includes a message list, on this page when users click a refresh button. I want to use AJAX to do that.
But I find that I could not call the function updatestatementlist(request) to retrieve the updated message list so it can be passed to the on this page.
/forum/views.py def updatestatementlist(request):
log.debug("call statementlist function")
statements = Statement.objects.filter(discussion=discussion)
return render(request, 'forum/statementlist.html', {
'statements': statements
})
I cannot see the log info so I think by clicking the button I fail to call this function.
The main html page of the discussion forum is /forum/discussion.html, which is under the template folder. I extract the html code within the div id = "chat" to a separate html /forum/statementlist.html as suggested here and several other SO posts.
/forum/discussion.html
<button id = "Refresh"> Refresh </button>
<div id="chat">
{% include 'forum/statementlist.html' %}
</div>
/forum/statementlist.html
{% load mptt_tags %}
{% recursetree statements %}
// display each statement
{% endrecursetree %}
forum.js
//When users click the refresh button
$("#Refresh").on("click", function(event){
alert("Refresh clicked")
$.ajax({
url: '',
type: "GET",
success: function(data) {
alert("success")
var html = $(data).filter('#chat').html();
$('#chat').html(html);
}
});
});
I also tried a few other url in this AJAX request: {% url updatestatementlist %}, {% url 'updatestatementlist' %}. But then I think it should be set to empty because I don't want to be redirected to another url. The discussion forum has a url of 'XXX.com/forum/roomY', by clicking the refresh button on this page I only want to refresh the div and fetch an updated statement list from the server.
BTW, I can see the two alerts after I click the button.
/forum/urls.py
urlpatterns = [
...
url(r'^(?P<label>[\w-]{,50})/$', views.discussion_forum, name='discussion_forum'),
url(r'^(?P<label>[\w-]{,50})/$', views.statementlist, name='statementlist'),
]
/forum/views.py def discussion_forum() is used to load all the information when the user first arrives at this forum.
I guess my problem might be that 1) the AJAX is wrong; 2) the url is wrong so that the updatestatementlist() can not be called.
Can anyone help me with that? Thanks a lot! Let me know if you need any other information!
Packages related:
Django==1.9.3
django-mptt==0.8.7
On the client side, Set your request header to X-Requested-With to XMLHttpRequest, Django use this specific header to determine whether it is a Ajax Request:
Here is the a snippet from Django source code:
https://docs.djangoproject.com/en/2.2/_modules/django/http/request/#HttpRequest.is_ajax
def is_ajax(self):
return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
After defining this header, you need to add one logic layer into your view function.
def your_view_func(request, *args, **kwargs):
if request.is_ajax():
...
return render(request, <your_ajax_template>)
return render(request, <your_normal_template>)
Updated:
I prefer the raw XMLHttpRequest API, if you use Jquery, add the berforeSend property.
$.ajax({
type: "GET",
beforeSend: function(request) {
request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
},
...
});
Why X-Requested-With but not HTTP_X_REQUESTED_WITH?
HTTP headers in the request are converted to META keys by converting all characters to uppercase, replacing any hyphens with underscores and adding an HTTP_ prefix to the name.
https://docs.djangoproject.com/en/2.2/ref/request-response/#django.http.HttpRequest.META
FYI - development site, work in progress.
I am using Django on a page with a form. However, I am also using a javascript tab function on this same page which highlights the tab with the correct on-page link / hash.
The default page = http://learntango.webfactional.com/video/35863752#section=comment
The contact-us page = http://learntango.webfactional.com/video/35863752#section=contact
When the form is not valid, the page is returned (good). However, it is the base page which is returned: http://learntango.webfactional.com/video/35863752
After that, the javascript then defaults this to the comment page.
How do I return the same page, with the "contact" on-page link active, so they can see their form errors and realize that the form did not submit?
Thanks!
David
def video(request, url_vimeo_id):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
send_mail(
'This is an e-mail from the website',
cd['message'],
cd.get('email', 'noreply#example.com'),
['dwyliu#gmail.com'],
)
return HttpResponseRedirect('http://www.learntodancetango.com')
return render_to_response('video.html',{'video': video,'next':next, 'form': form}, context_instance=RequestContext(request))
else:
form = ContactForm()
return render_to_response('video.html',{'video': video,'next':next, 'form': form}, context_instance=RequestContext(request))
Set form's action as http://learntango.webfactional.com/video/35863752#section=contact