Ok, so I'm trying to add voting to my website(django based) using Ajax. I have multiple entries in one page, But right now my code only let users vote on the first entry. Please help me with the code so that users can vote on all of them.
First is the html code, basically it's just a vote button for users to vote
{% for answer in answers %}<!-- django template -->
<strong id="vote_count">{{ answer.votes }}</strong> people vote this answer
{% if user.is_authenticated %}
<button id="vote" data-answerid="{{answer.id}}" class="btn btn-primary" type="button">
<span class="glyphicon glyphicon-thumbs-up"></span>
Vote
</button>
{% endif %}
{% endfor %}<!-- end django template -->
Second, below is the django view that process the request
#login_required
def vote_answer(request):
answer_id = None
if request.method == 'GET':
answer_id = request.GET['answer_id']
votes = 0
if answer_id:
answer = Answer.objects.get(id=answer_id)
if answer:
votes = answer.votes + 1
answer.votes = votes
answer.save()
return HttpResponse(votes)
below is the url mapping:
url(r'^like_category/$', views.like_category, name='like_category'),
Finally is the javascript
$('#vote').click(function(){
var answerid;
answerid = $(this).attr("data-answerid");
$.get('/vote_answer/', {answer_id: answerid}, function(data){
$('#vote_count').html(data);
$('#vote').hide();
});
});
Again, my problem is that of all the entries I have in one page, with this code I can only vote the first one. How can modify it so I can vote all of them
You need to use class instead of id on <button>, so that multiple buttons can share the same jQuery event handler.
<button class="vote" data-answerid="...">
Then you can do the following in JavaScript:
$(document).on("click", ".vote", function(){
var answerid;
answerid = $(this).attr("data-answerid");
$.get('/vote_answer/', {answer_id: answerid}, function(data){
$('#vote_count').html(data);
$('#vote').hide();
});
});
This will bind the event handler to click any <button class=vote>.
Also you should do AJAX POST instead of GET by HTTP semantics, because voting is a state changing operation. Otherwise the browser or the web proxies may cache the result (though jQuery have its own cache buster).
Related
I am developing on localhost: http://127.0.0.1:8000
When I perform some search on the website, I want that if the query does not return results, the New entry button allows the creation of a new article from what has been searched in the search input.
So if I search 'whatever' and there is no article called 'whatever', the button should redirect me to the creation page of the article 'whatever'.
<script>
$( document ).ready(function() {
var newEntryUrl = window.location.hostname+":8000/wiki/_create/?slug="+"{{ search_query }}";
document.getElementById('newEntrybtn').setAttribute('href',newEntryUrl);
});
</script>
{% for article in articles %}
{% block wiki_search_loop2 %}
{% endblock %}
{% empty%}
There is no page created for '{{ search_query }}', would you like to create a page nowee?
<a class="btn btn-success" id="newEntrybtn" role="button"><i class="fa fa-plus" aria-hidden="true"></i> New entry ({{ search_query }})</a>
{% endfor %}
To calculate the url to create the new article, I use this line:
var newEntryUrl = window.location.hostname+":8000/wiki/_create/?slug="+"{{ search_query }}";
If I do an alert(newEntryUrl); it returns the desired result: 127.0.0.1:8000/wiki/_create/?slug=whatever
However, if I click the newEntrybtn button, it redirects me to the following url: http://127.0.0.1:8000/wiki/_search/127.0.0.1:8000/wiki/_create/?slug=whatever
Which is strange to me since at no time have I assigned the href attribute to the button, much less have I assigned it any value. It seems that somehow, by default, it gets the value of the current page.
My question is, how can I remove the current page: http://127.0.0.1:8000/wiki/_search/ so that the button href just has this structure: 127.0.0.1:8000/wiki/_create/?slug=whatever ?
I think you are appending the value twice. Once when the page loads because it is in the document.ready function and once again when the button is clicked. Try writing it in another function and calling the function when the button is clicked.
setAttribute is being called twice
https://codepen.io/sijbc/pen/zYNdrmz
.setAttribute() is being called twice
function createUrl(){
var newEntryUrl = window.location.hostname+":8000/wiki/_create/?slug="+"{{ search_query }}";
var newEntryBtn = document.getElementById('newEntrybtn')
newEntryBtn.addEventListener("click" function(){
newEntryBtn.setAttribute('href',newEntryUrl);
})
The problem is because you are using a relative URL as the href, causing the browser to append this to the current URL you are looking at.
In your example, your button will be set as follows:
<a class="btn btn-success" id="newEntrybtn" role="button" href="127.0.0.1/...">
And clicking on it will append the href to the current URL since the browser will consider it as a resource of the current page.
Following your example, using an absolute URL will allow you to go directly to the URL as you have it set as long as you know the full structure (in your case, you are missing the protocol/scheme):
<a class="btn btn-success" id="newEntrybtn" role="button" href="http://127.0.0.1/...">
Or ideally you should use a relative URL by defining correctly the segment of the URL it represents (defining the path from your host where the resource is located):
<a class="btn btn-success" id="newEntrybtn" role="button" href="/wiki/_create/...">
(notice how the URL starts with a slash and omits the server host and protocol).
It is preferred in most cases to use relative URLs to make your code run regardless of the server (environment), meaning it will always use the same server or protocol of the current URL. But it's up to you based on your needs.
References:
https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_URL#absolute_urls_vs_relative_urls
I found a good solution without using JavaScript, in just one line of code:
<a class="btn btn-success" href="/wiki/_create/?slug={{ search_query }}" role="button"><i class="fa fa-plus" aria-hidden="true"></i> New entry ({{ search_query }})</a>
Thanks to those who have posted an answer, you have inspired me a lot.
I'm creating a website using Django, and using Ajax to prevent site reload after submitting a form. Right now, I have orders being displayed on the site with an x button beside each order. Clicking the x cancels the order on the database (a post request that changes a value rather than simply deleting it) and also reloads the div in which the orders are housed. I have other forms on this website that are working correctly (they do have fields, though and use crispyforms). The problem I'm facing is that the script isn't detecting that the form is submitted.
Here are the pertinent parts of my project:
views.py
class CancelForm(ModelForm):
class Meta:
model = Order
fields = ['Filled']
...
def cancelorder(request, pk):
form = CancelForm(request.POST)
if request.is_ajax and request.method == "POST":
order = Order.objects.get(pk=pk)
order.Filled = "C"
instance = order.save(update_fields=["Filled"])
return JsonResponse({"canceled": pk}, status=200)
return JsonResponse({"error": ""}, status=400)
urls.py
urlpatterns = [
path('', views.orderpage, name="order-index"),
path('cancel_order/<int:pk>/', views.cancelorder, name="cancel_order"),
path('post/ajax/order/', views.postorder, name = "post_order"),
path('yourorders/', views.yourorders, name="your_orders"),
path('allorders/', views.allorders, name="all_orders"),
]
orderpage.html (this is my main page, with the div that is to be reloaded on yourorders.html)
<div class="container-fluid ActiveOrderInfoDiv" id="YourOrdersDiv">
{% include 'order/yourorders.html' %}
</div>
yourorders.html
{% for order in all_orders %}
<div class="row">
...
<div class="col">{{ order.OrderID }}</div>
<form action="{% url 'order:cancel_order' pk=order.OrderID %}" id="cancel_button" method="POST">
{% csrf_token %}
<button type="submit" class="close" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</form>
</div>
{% endfor %}
Javascript (here, check 2 is never logged)
$(document).ready(function () {
$("#cancel_button").submit(function (e) {
console.log("check 2");
e.preventDefault();
console.log("check 3");
$.ajax({
url: "{% url 'order:your_orders' %}",
success: function (response) {
$("#YourOrdersDiv").load("/order/yourorders/");
},
error: function (response) {
}
});
});
});
What I've tried
Moved the script to yourorders.html (I thought maybe the JS wasn't seeing the cancel_button ID)
Used console.log to see where the flow stopped (it doesn't seem to pick up that the cancel button was submitted)
Added the CancelForm modelform (previously I was updating the DB without a modelform)
Generally poking around with Ajax syntax and order
Looking at other questions here on StackOverflow - I seem to be following them, and my syntax may just be right, it's just not picking up that the cancel_button form is being submitted
You should delegate the event handler to the document level so that when the form is reloaded the event is still handled. When you "reload" the form you are inserting a new element into the DOM that does not have the event handler attached to it
$(document).on("submit", "#cancel_button", function(e) {
...
I have a cripsy form and I want to change one field from Textarea to CKEDitorUploadingWdidget
So my form looks like this (I have left in what was previoulsy working:
class RenameStudyForm(BetterModelForm):
name = forms.CharField(label='Study Name', max_length=51, required=False) # Update study name
#waiver = forms.CharField(widget=forms.Textarea, label='Waiver of Documentation', required=False)
waiver = forms.CharField(widget=CKEditorUploadingWidget(), label='Waiver of Documentation', required=False)
I have amended my model as follows:
class study(models.Model):
researcher = models.ForeignKey("auth.user") # Researcher's name
name = models.CharField(max_length = 51) # Study name
instrument = models.ForeignKey("instrument") # Instrument associated with study
#waiver = models.TextField(blank = True)
waiver = RichTextUploadingField(blank = True)
My template looks has:
{% load crispy_forms_tags %}
{{ form.media }}
{% crispy form %}
When I enter the screen to edit the waiver I get a rich text field to edit, as I would expect. However, nothing I enter into the field is passed back to the form. Within the form I added a print statement, as below
def clean(self):
cleaned_data = super(RenameStudyForm, self).clean()
print(cleaned_data['waiver'])
The print always gives the original text. Can anyone help me please
EDIT
I've been reviewing console when I'm using the CKEditorUploadingWidget against the forms.Textarea widget and it appears to be generating the following jQuery warning
Synchronous XMLHttpRequest on the main thread is deprecated because of
its detrimental effects to the end user's experience.
I believe I am getting this because I am loading the form into a modal using this button
<button type="button" class="btn btn-secondary btn-block" onclick = "modal_form('/interface/study/{{ current_study|urlencode }}/rename_study/')" >Update Study</button>
And this view
def rename_study(request, study_name):
#do stuff
return render(request, 'researcher_UI/add_study_modal.html', form_package)
So my JavaScript for ckeditor is being loaded now rather than when the document is originally loaded so I think this causes the issues. Any thoughts really appreciated
Found the answer. The form is being submitted via ajax. As such I need to copy the CKEditor data into the form field, which I do with
for (var instance in CKEDITOR.instances){
CKEDITOR.instances[instance].updateElement();
}
The problem is due to the ajax implementation in my django twitter clone app, the like count for every post is showing the same after clicking the like button.but after the page refresh that is okay. I am near to solve the problem but stuck somehow.
view:
def add_like(request):
if request.method == 'GET':
ans_id = request.GET['id']
user = request.user.profile
liked_tweet = get_object_or_404(Tweet, pk=ans_id)
if ans_id:
# creating instance by sending the Like table fields
instance, created = Like.objects.get_or_create(liker=user, liked_tweet=liked_tweet)
ans = Tweet.objects.get(id=(int(ans_id)))
if ans:
likes = ans.likes + 1
ans.likes = likes
ans.save()
# returns the likes field of a tweet post
return HttpResponse(likes)
the HttpResponse is sending the likes and that creates the problem I guess.
the template:
{% for tw in tweets %}
<div class="blog-post">
<p>
{{ tw.content|safe }}<br><hr>
<small class="small">
লিখসে -
<!-- in the "href" we can pass data like "pk", accessing by the structure the current object is based on-->
{{ tw.tweeter.user.username|capfirst }}
</small>
</p>
{% if user.is_authenticated %}
<button class="btn btn-default likes-button" type="button"
data-ansid="{{ tw.pk }}">Like</button>
<i> Total Likes: </i><em class="like_count">{{ tw.likes }}</em>
{% endif %}
</div>
the ajax script:
$(".likes-button").click(function(e) {
if ($(this).html() == "Like") {
$(this).html('Unlike');
//alert("js working");
// error was there for "data" insted of "attr"
var ansid = $(this).attr("data-ansid");
$.ajax({
url: '{% url "add_like" %}',
type: 'get',
data: {id: ansid}
}).done(function (data) {
alert("success");
$('.like_count').html(data);
//$('#likes').hide();
}).fail(function (err) {
alert(err);
});
}
Thanks in advance.
I think the very first comment by Sayse gives your answer. I am just trying to give a bit more explanation.
So What you have done is after a successful ajax request, you replace existing like count with the data you get from ajax in any the element who have a class named .like_count.
Check In your code $('.like_count').html(data); This select all the elemnt having like_count class and change the html.
Instead, what you should've done is after a successful ajax, change the data only in one place. You need to choose appropriate jquery selector.
Something like .closest() can be used. In that case, use (following code is not tested) $(this).closest('.like_count').html(data); to apeend 'like count' in appropriate element.
Also you can assign dynamic ID to each 'like count' element based on id and then use exect ID Selector.
Hope this helps.
Cheers!
Did you say:
but after the page refresh that is okay
Since your code snippet works, you're simply looking for the likes count incrementation to happen and see the live update in the template.
Well, in theory, here:
The function that increments the like should return with a JSON response of the incremented value from the database.
A client function standing by accepts this JSON response, and updates the template value accordingly.
In Practicals:
See this answer: https://stackoverflow.com/a/31832275/1757321
I have a Song model with a votes attribute. I have a Vote as Favourite button displayed below each Song object. I want when a user clicks on the Vote as Favourite button the votes attribute associated with that Song object should increment by 1 and all the Vote as Favourite buttons should be disabled.
HTML
{% for song in dj_song_list %}
<div>
<p><h3>{{ song.name }}</h3></p>
<p class="song_id">{{ song.song_id }}</p>
<button type="button" class="btn btn-default btn-custom" class='vote' onclick="update();">Vote as Favourite</button>
</div>
{% endfor %}
ajax.py
def update_votes(id):
song = Song.objects.get(song_id=id)
song.votes += 1
song.save()
#dajaxice_register
def update_disable(request, song_id):
update_votes(song_id)
dajax = Dajax()
dajax.assign('.vote', 'disabled', 'disabled')
return dajax.json()
JavaScript
function update(){
Dajaxice.hunt.update_disable(Dajax.process,{'song_id':$('.song_id').val()})
}
The button disabling part works fine when used alone. But when I use the update_votes() function in the update_disable() function nothing works. What is wrong with my code?
AJAX part does nothing, because it gets errors. The problem is in the update_votes() function. The problems could be:
Several objects exist with same song_id value
The song with id does not exist
Try using pdb.set_trace() for debugging purposes:
def update_votes(id):
import pdb; pdb.set_trace()
song = Song.objects.get(song_id=id)
I had set the votes attribute of the Song model to default=None. Hence it wasn't able to perform the addition operation. I set it now as default=0 instead.