AJAX and Django, AJAX success not working - javascript

In an HTML page I have a table where one can drop items into and that is posted to the server via AJAX as follows:
function drop(ev, el) {
if (ev.target.tagName=="IMG") { return; }
ev.preventDefault();
var data = ev.dataTransfer.getData("text");
el.appendChild(document.getElementById(data));
$.ajax({
type: 'POST',
url: server_url + currentPath,
data: {
'version': data
},
success() {
console.log(data, "ranked ");
},
});
}
on the same page, users submit an answer using a button which is linked to a function that sends a POST request to the server:
function NextPage(){
answer = $("#justification").val();
if (answer.length < 10){
document.getElementById("warning1").innerHTML = "Please provide an answer";
}
else {
$.ajax({
type: "POST",
url: server_url + currentPath,
data: {'answer' : answer},
success: function () {
window.location.href = server_url+ nextPath;
}
});
}
}
I can read the data from the post request on the server side (i.e., version and answer), and it gets saved as well, but then I cannot move to the next page, and there are no error messages; I noticed that the answer is now passed as a parameter on the URL; not sure what that means. I guess it gets stuck because of an error. I checked the currentPath and NextPath and they are correct. I think the problem is with the ranking object, because when I removed it, everything worked perfectly fine. Can someone spot something I am missing that caused this issue?
here is my view:
#csrf_exempt
#login_required
def task_view(request ,visualisation, task, progress):
participant = get_object_or_404(Participant, user=request.user)
question_list = Question.objects.filter(complexity = 'N')
question = question_list[0]
answer, created = Answer.objects.get_or_create(participant=participant, question=question)
ranked_visualisation = SVG.objects.filter(name=visualisation, task=task)
if request.method == 'POST':
participant_answer = request.POST.get('answer')
current_version = request.POST.get('version')
for i in ranked_visualisation:
ranking, created = Ranking.objects.get_or_create(participant=participant, svg=i)
if ranking.svg.version == current_version:
ranking.ranking = 10
else:
ranking.ranking = 20
ranking.save()
tasks_completed = participant.tasks_completed
if participant_answer and not participant_answer.isspace():
tasks_completed = tasks_completed +1
else:
tasks_completed = tasks_completed
participant.tasks_completed = tasks_completed
participant.save()
answer.answer = participant_answer
answer.save()
context = {'task':task,'question': question, 'answer': answer, 'progress': progress}
return render(request, 'study/A.html', context)
here are my models:
class SVG(models.Model):
name = models.CharField(max_length=200)
version = models.CharField(max_length=200, default='none')
task = models.CharField(max_length=200, default='none')
def __str__(self):
return self.name
class Ranking(models.Model):
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
ranking = models.IntegerField(default=0)
svg = models.ForeignKey(SVG, on_delete=models.CASCADE)
class Answer(models.Model):
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
question = models.ForeignKey(Question, on_delete=models.CASCADE)
answer = models.TextField(blank=True, null=True)

Related

Why an event handler function in consumers.py is called multiple times and then web-socket connection is killed forcefully?

I have created a real time notifier using web-socket, implemented using Django-Channels. It sends the "Post Author" a real time notification whenever any user likes the author's post.
The problem is, for single like button click, multiple notifications are being saved in database. This is because the function (like_notif()) defined in consumers.py which is saving notification data to database when the 'like button click' event happens is called multiple times (when it should be called only once) until the web-socket is disconnected.
In the following lines I will provide detail of what is actually happening(to the best of my understanding) behind the scene.
Like button is clicked.
<a class="like-btn" id="like-btn-{{ post.pk }}" data-likes="{{ post.likes.count }}" href="{% url 'like_toggle' post.slug %}">
Like
</a>
JavaScript prevents the default action and generates an AJAX call to a URL.
$('.like-btn').click(function (event) {
event.preventDefault();
var this_ = $(this);
var url = this_.attr('href');
var likesCount = parseInt(this_.attr('data-likes')) || 0;
$.ajax({
url: url,
method: "GET",
data: {},
success: function (json) {
// DOM is manipulated accordingly.
},
error: function (json) {
// Error.
}
});
});
The URL is mapped to a function defined in views.py. (Note: Code for Post Model is at the last if required for reference.)
# In urls.py
urlpatterns = [
path('<slug:slug>/like/', views.post_like_toggle, name="like_toggle"),
]
# In views.py
#login_required
def post_like_toggle(request, slug):
"""
Function to like/unlike posts using AJAX.
"""
post = Post.objects.get(slug=slug)
user = request.user
if user in post.likes.all():
# Like removed
post.likes.remove(user)
else:
# Like Added
post.likes.add(user)
user_profile = get_object_or_404(UserProfile, user=user)
# If Post author is not same as user liking the post
if str(user_profile.user) != str(post.author):
# Sending notification to post author is the post is liked.
channel_layer = get_channel_layer()
text_dict = {
# Contains some data in JSON format to be sent.
}
async_to_sync(channel_layer.group_send)(
"like_notif", {
"type": "notif_like",
"text": json.dumps(text_dict),
}
)
response_data = {
# Data sent to AJAX success/error function.
}
return JsonResponse(response_data)
Consumer created to handle this in consumers.py.
# In consumers.py
class LikeNotificationConsumer(AsyncConsumer):
async def websocket_connect(self, event):
print("connect", event)
await self.channel_layer.group_add("like_notif", self.channel_name)
await self.send({
"type": "websocket.accept",
})
async def websocket_receive(self, event):
print("receive", event)
async def websocket_disconnect(self, event):
print("disconnect", event)
await self.channel_layer.group_discard("like_notif", self.channel_name)
async def notif_like(self, event):
print("like_notif_send", event)
await self.send({
"type": "websocket.send",
"text": event.get('text')
})
json_dict = json.loads(event.get('text'))
recipient_username = json_dict.get('recipient_username')
recipient = await self.get_user(recipient_username)
sender_username = json_dict.get('sender_username')
sender = await self.get_user(sender_username)
post_pk = json_dict.get('post_pk', None)
post = await self.get_post(post_pk)
verb = json_dict.get('verb')
description = json_dict.get('description', None)
data_dict = json_dict.get('data', None)
data = json.dumps(data_dict)
await self.create_notif(recipient, sender, verb, post, description, data)
#database_sync_to_async
def get_user(self, username_):
return User.objects.get(username=username_)
#database_sync_to_async
def get_post(self, pk_):
return Post.objects.get(pk=pk_)
#database_sync_to_async
def create_notif(self, recipient, sender, verb, post=None, description=None, data=None, *args, **kwargs):
return Notification.objects.create(recipient=recipient, sender=sender, post=post, verb=verb, description=description, data=data)
Note: notif_like() function is being called multiple times and thus data is being saved multiple times in the database. Why is this function being called multiple times?
JavaScript code(written in HTML file) handling websocket connection. I have used Reconnecting Websockets .
<script type="text/javascript">
var loc = window.location
var wsStart = 'ws://'
if(loc.protocol == 'https:') {
wsStart = 'wss://'
}
var endpoint = wsStart + loc.host + "/like_notification/"
var likeSocket = new ReconnectingWebSocket(endpoint)
likeSocket.onmessage = function (e) {
console.log("message LikeNotificationConsumer", e)
var data_dict = JSON.parse(e.data)
/*
DOM manipulation using data from data_dict.
NOTE: All the intended data is coming through perfectly fine.
*/
};
likeSocket.onopen = function (e) {
console.log("opened LikeNotificationConsumer", e)
}
likeSocket.onerror = function (e) {
console.log("error LikeNotificationConsumer", e)
}
likeSocket.onclose = function (e) {
console.log("close LikeNotificationConsumer", e)
}
</script>
Post model for reference
# In models.py
class Post(models.Model):
title = models.CharField(max_length=255, blank=False, null=True, verbose_name='Post Title')
post_content = RichTextUploadingField(blank=False, null=True, verbose_name='Post Content')
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
default=None,
blank=True,
null=True
)
likes = models.ManyToManyField(User, blank=True, related_name='post_likes')
published = models.DateTimeField(default=datetime.now, blank=True)
tags = models.ManyToManyField(Tags, verbose_name='Post Tags', blank=True, default=None)
slug = models.SlugField(default='', blank=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.title + str(self.pk))
super(Post, self).save()
def get_like_url(self):
return reverse("like_toggle", (), {'slug', self.slug})
def __str__(self):
return '%s' % self.title
Notification model for reference
# In models.py
class Notification(models.Model):
recipient = models.ForeignKey(User, blank=False, on_delete=models.CASCADE, related_name="Recipient",)
sender = models.ForeignKey(
User,
blank=False,
on_delete=models.CASCADE,
related_name="Sender"
)
# Post (If notification is attached to some post)
post = models.ForeignKey(
Post,
blank=True,
default=None,
on_delete=models.CASCADE,
related_name="Post",
)
verb = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
data = models.TextField(blank=True, null=True)
timestamp = models.DateTimeField(default=timezone.now)
unread = models.BooleanField(default=True, blank=False, db_index=True)
def __str__(self):
return self.verb
Channels layer setting.
# In settings.py
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("localhost", 6379)]
}
}
}
I expect the notification to be saved only once per click on the like button but it is being saved multiple times as the notif_like() function defined in consumers.py is being called multiple times per click.
Moreover, web-socket gets disconnected abruptly after this and following warning is displayed in the terminal -
Application instance <Task pending coro=<SessionMiddlewareInstance.__call__() running at /home/pirateksh/DjangoProjects/ccwebsite/ccenv/lib/python3.7/site-packages/channels/sessions.py:183> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fb7603af1c8>()]>> for connection <WebSocketProtocol client=['127.0.0.1', 52114] path=b'/like_notification/'> took too long to shut down and was killed.
I am struggling with this for quite a time now, so it will be really helpful if someone can guide me through this.
After almost a year I was working on a very similar project and I was able to reproduce this bug and also figured out what was causing this.
I am pretty sure that this is somehow being caused by ReconnectingWebSocket.
The reason for this is that when I switched back to using vanilla WebSocket, everything worked fine.
I have also opened an issue regarding this on the ReconnectingWebSocket's Github repository.

Object of type 'Query' is not JSON serializable

This is my routes.py
#app.route('/test', methods=['POST', 'GET'])
def test():
# form = BrandChoice()
# email = request.form['email']
# name = request.form['name']
choice = request.form['choice']
print(choice)
q = session.query(Brand.brand).filter(Brand.pid == choice)
print(q)
return jsonify({'choice': q })
And this is my test.js file
$(document).ready(function() {
$('form').on('submit', function(event) {
$.ajax({
data : {
name : $('#nameInput').val(),
email : $('#emailInput').val(),
choice : $('#brandInput').val()
},
type : 'POST',
url : '/test'
})
.done(function(data) {
if (data.error) {
$('#errorAlert').text(data.error).show();
$('#successAlert').hide();
}
else {
$('#errorAlert').text(data.choice).show();
$('#successAlert').text(data.name).show();
// $('#errorAlert').hide();
}
});
event.preventDefault();
});
});
I don't quite get why is it that I cannot get the query result to show up. If I were to replace my 'q' with name for example, it works as intended.
How do I go about returning my query result so that I can display it in my errorAlert?
Thank you for the help everyone. I understand the issue with my code right now. q is a Query object. I did not even execute the query.
type_pid = session.query(LenSize.type_pid).filter(LenSize.pid == choice)
type_pid_result = session.execute(type_pid)
type_pid_count = type_pid_result.first()[0]
print(type_pid_count)
q = session.query(Type.initial).filter(Type.pid == type_pid_count)
print(q)
result = session.execute(q)
print(result)
id_count = result.first()[0]
print(id_count)
return jsonify({'name': id_count})
The amendments I have made was to execute my query and in return I would get a result proxy. Apply the first() method to obtain my desired output. That output was JSON serialisable. No changes were really made to my js file. A simplified version of my correction is below. Hope this helps!
q = session.query(Model.id).filter(Model.pid == choice)
result = session.execute(q)
row_id = result.first()[0]

Cant get ajax response to place nice with python code

Question: For some reason I can't seem to figure out why my ajax call will not return a value for success if my wo_data query comes up empty. What I want to happen is if I put a value into id_work_order and its not found in my db pop up an alert saying workorder was not found- otherwise if it is found return success which works. Any help would be greatly appreciated!
What happens is I get this if a workorder is not found in my browser console
typeerror: data[0] is undefined
if it is found I get this
success
Here is what my data looks like if the workorder is found in my response
0: object
purch_order: "1"
success: "success"
part_rev: "A"
part_number: "12345"
work_o: "W0000001"
customer_name: "TEST"
if it isn't found I get nothing back in my response
here is my views.py
def get_work(request):
if request.is_ajax():
q = request.GET.get('workorder_id', '')
wo_data = Dim_work_order.objects.filter(base_id__icontains = q )[:1]
results = []
for x in wo_data:
x_json = {}
if wo_data.exists():
x_json['success'] = 'success'
x_json['work_o'] = x.base_id
x_json['customer_name'] = x.name
x_json['part_number'] = x.part_id
x_json['part_rev'] = x.part_rev
x_json['purch_order'] = x.customer_po_ref
results.append(x_json)
else:
x_json['success'] = 'workorder_not_found'
results.append(x_json)
data = json.dumps(results)
mimetype = 'application/json'
return HttpResponse(data, mimetype)
else:
data = 'fail'
return render(request, 'app/sheet_form_create')
here is my workorder_ajax.js
$(document).ready(function () {
//$('#id_work_order').click(function () {
// getwork();
//});
$('#work_search').click(function () {
pop_other_fields();
});
//function getwork(){
// $('#id_work_order').autocomplete({
// source: "/sheet/sheet_form_create.html/get_work",
// minLenght: 2,
// });
//}
function pop_other_fields() {
var url = "/sheet/sheet_form_create.html/get_work?workorder_id=" + $('#id_work_order').val();
var work_order = document.getElementById('id_work_order').value;
$.ajax({
type: 'GET',
url: url,
dataType: 'json',
data: '',
success: function (data) {
if (data[0].success = "success") {
console.log(data[0].success);
$('#id_customer_name').val(data[0].customer_name);
$('#id_part_number').val(data[0].part_number);
$('#id_part_revision').val(data[0].part_rev);
$('#id_purchase_order').val(data[0].purch_order);
}
if (data.success = "workorder_not_found") {
alert("workorder not found :(")
}
}
});
}
});
the code here is never reached:
else:
x_json['success'] = 'workorder_not_found'
results.append(x_json)
because if if wo_data.exists(): is not true, then for x in wo_data: would never have any iterations in the first place.
Try:
def get_work(request):
if request.is_ajax():
q = request.GET.get('workorder_id', '')
wo_data = Dim_work_order.objects.filter(base_id__icontains = q )[:1]
results = []
if wo_data.exists():
for x in wo_data:
x_json = {}
x_json['success'] = 'success'
x_json['work_o'] = x.base_id
x_json['customer_name'] = x.name
x_json['part_number'] = x.part_id
x_json['part_rev'] = x.part_rev
x_json['purch_order'] = x.customer_po_ref
results.append(x_json)
else:
results.append({'success': 'workorder_not_found'})
data = json.dumps(results)
mimetype = 'application/json'
return HttpResponse(data, mimetype)

Making a POST request with jQuery+HTML to display JSON data

I want to display json data on frontend but after post request though it is successful it's giving specific data I need to generalize the code.This is python code.
import json
from flask import Flask, render_template, request, jsonify
import requests
app = Flask(__name__)
def post(url, payload):
returnData={}
if url == 'http://api/my-general-api':
r = requests.post(url, data=json.dumps(payload))
else:
r = requests.get(url)
#r = requests.get()
if r.status_code == 200:
returnData["status"] = "SUCCESS"
returnData["result"] = json.loads(r.text)
else:
returnData["status"] = "ERROR"
return returnData
def processSingleHost(perfid, hostname, iteration):
hostsData = {}
payload = {
"perfid" : perfid,
"section" : {
"hostname" : hostname,
"iteration" : iteration,
"sectionname" : "sysstat_M"
}
}
returnData = post('http://api/my-general-api', payload)
payload = {
"perfid" : perfid,
"section" : {
"hostname" : hostname,
"iteration" : iteration,
"sectionname" : "sysstat_x_1sec"
}
}
returnData1 = post('http://api/my-general-api', payload)
return {
"status" : "SUCCESS",
"sysstat_M" : returnData,
"sysstat_x_1sec" : returnData1
}
#app.route("/",methods=['GET','POST'])
def home():
if request.method == 'POST':
#user inputs
value1 = request.form.get('perfid')
value2 = request.form.get('hostname')
value3 = request.form.get('iteration')
#api call
url1 = 'http://api/my-general-api'/{0}'.format(value1)
payload= {}
rdata = post(url1,payload)
hostsData = {}
if rdata["status"] == "SUCCESS":
for item in rdata["result"]:
for host in item["hosts"]:
hostsData[host["hostname"]] = processSingleHost(value1,host["hostname"], 1) //here hostname contain specific value for specific host
else:
return "";
return jsonify(hostname=hostsData); // returning all host values
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
This is my .js file for accessing data :
$(document).ready(function() {
console.log("ready!");
$('form').on('submit', function() {
console.log("the form has beeen submitted");
// grab values
valueOne = $('input[name="perfid"]').val();
valueTwo = $('input[name="hostname"]').val();
valueThree = $('input[name="iteration"]').val();
console.log(valueOne)
console.log(valueTwo)
console.log(valueThree)
$.ajax({
type: "POST",
url: "/",
dataType:'json',
data : { 'perfid': valueOne,'hostname': valueTwo,'iteration': valueThree},
success: function(data) {
var x = parseInt(data.hostname.sysstat_M.result.sectoutput.summarystats.Avg.AVG); //here hostname is variable I am planning to use that will help to generalize access.
if(x>80)
{
var p = '<p><div class="isa_warning"><i class="fa fa-warning"></i>CPU may be overloading.</div></p>';
$('#result').append(p);
}
else
{
var p = '<div class="isa_success"><i class="fa fa-check"></i>CPU Usage is Normal.</div><p></p>';
$('#result').append(p);
}
},
error: function(error) {
console.log(error)
}
});
});
});
$('input[type="reset"]').on('click', function(e){
e.preventDefault();
$('#result').empty();
})
But as screenshot showing it demands me to make access specifically by giving hostname = 10.161.146.94/10.161.146.90
As mention in above .js
var x = parseInt(data.hostname.10.161.146.94/10.161.146.90.sysstat_M.result.sectoutput.summarystats.Avg.AVG);
But in future this hostname will be different.So I need to generalize it what can I do please suggest ??
SideNote:
If you are using hostname or IPs to identify each client its not adviced; since it is ought to fail.
You must use sessions for that.
Anyways, if your simply looking for how you can modify your javascript code to access the JSON response when you are not aware of the keys:
for(key in data){
console.log(key);
console.dir(data[key]);
}
Edit:
Showing in select box using jQuery can be done as:
var options = "";
for (key in data) {
options += '<option value="' + key + '">' + key + '</option>';
}
$("#my_dropdown").html(options);

Implement notifications through ajax in django

I have a feed stream in which users are shown activities, I have to notify users of new activity after the page is loaded through ajax call.
Below is the code
views.py
#login_required
def new_activity(request):
context = {}
response = {}
latest_activity = []
prev_activity_id = request.GET.get('activity_id')
#get the latest activity from news feed
time_stamp = Activity.objects.get(pk = prev_activity_id).created
latest_activity = Activity.objects.filter(created__gt = time_stamp)
activity_count = latest_activity.count()
#Handle multiple activities
if activity_count == 1:
updated_id = latest_activity.id
elif activity_count > 1:
updated_id = latest_activity[0].id
else:
updated_id = prev_activity_id
context['activities'] = latest_activity
template = render_to_string('activities/new_activity_template.html', context, context_instance=RequestContext(request))
response['template'] = template
response['updated_id'] = updated_id
response['activity_count'] = activity_count
response['success'] = 'success'
return HttpResponse(simplejson.dumps(response), mimetype='application/json')
ajax
(function new_activity() {
setTimeout(function() {
var activity_id = null
$.ajax({
url: "/activities/all/new_activity/?activity_id="+activity_id,
type: "GET",
success: function(response) {
$('#id_new_activity').empty();
$('#id_new_activity').append(response.template);
console.log(response.success);
//if activity_id is null get the activity id for first time
//if activity_id is not null update the activity_id from the response
activity_id = response.updated_id;
},
dataType: "json",
complete: new_activity,
timeout: 2000
})
}, 5000);
})();
I need to update the latest acitivity_id after each call to new_activity if any new object is created in my activity model. Also i need to pass the activity_id for the first time when page is loaded.
I am not sure how to do that in my ajax call.
Save not activity_id, but latest_activity date in user browser. When ajax queried and there is an new activity, save new date of latest activity in user browser, and for next ajax query send that date, so server-side app will send only activity after that date.

Categories

Resources