I'm trying to implement a comment system without reloading the page, but it doesn't work. Comments are inserted only after the page is reloaded, but AJAX does not work.
<!-- COMMENT -->
<div >
<form action="{% url 'love:comment_urls' %}" method="POST" class='comment-form'>
{% csrf_token %}
<input type="hidden" name="post_id" value={{i.pk}}>
{{ comment_form }}
<button type="submit" name="submit_c_form">
Опубликовать
</button>
</form>
</div>
<hr>
<div >
{% if i.quotes_comment.all %}
{% for com in i.quotes_comment.all %}
<b>{{ com.user }}:</b>
{{ com.body }}
{% endfor %}
{% endif %}
</div>
</div>
JS code
// COMMENT
$(document).ready(function () {
$('.comment-form').submit(function () {
event.preventDefault();
console.log($(this).serialize());
var url = $(this).attr('action')
$.ajax({
type: 'POST',
url: url,
data: $(this).serialize(),
dataType: 'html',
success: function (response) {
console.log('Заработало')
$('.comment-section').html(response['form']);
},
error: function (rs, error) {
console.log(rs, error)
}
})
})
})
@csrf_exempt
def create_comment(request):
profile = Profile.objects.get(user=request.user)
data = json.loads(request.body)
post_id = data['post_id']
body = data['body']
comment_form = CommentModelForm(data)
if comment_form.is_valid():
print('Form is valid')
instance = comment_form.save(commit=False)
instance.user = profile
instance.quotes = QuotesHonors.objects.get(
id=post_id)
instance.save()
comment_form = CommentModelForm()
return JsonResponse({
'content': body
})
return redirect("love:home_urls")
As I said, comments are inserted only after a reboot, but when I click publish, nothing happens. If you change the dataType to 'json', the parser error pops up. Can you explain what I'm doing wrong?
CodePudding user response:
Hard to say what you are doing wrong overall there is code missing. But from what you shared, csrf_token
is not in AJAX request header
. And mainly, by using JQuery .serialize(), you are trying to pass data in URL-encoded notation and not as a JSON object.
This .serialize()
output would be used to pass data as a query string by appending it to a base URL. And, at the view accessing it with request.GET.get('parameter')
. Instead, what we want is to pass data as JSON
and access it through request.body
at view level.
Here is a full example:
models.py
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
title = models.CharField(max_length=30)
body = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=timezone.now())
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='comments')
body = models.CharField(max_length=255)
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments', blank=True)
created_at = models.DateTimeField(auto_now_add=timezone.now())
urls.py
from .views import post_detail, comment_create
app_name = 'love'
urlpatterns = [
path('post/<int:pk>/', post_detail, name='post-detail'),
path('comment/create/', comment_create, name='comment-create'),
]
Two views, one to render the template and another to handle the AJAX request. The tricky part was trying to find a way to format date in the same way as the template.
views.py
from django.utils.formats import date_format
from django.shortcuts import get_object_or_404
from django.http import JsonResponse
import json
def post_detail(request, pk):
post = get_object_or_404(Post, id=pk)
has_commented = post.comments.filter(user=request.user).exists()
context = {
'post': post,
'has_commented': has_commented
}
return render(request, 'post_detail.html', context)
def comment_create(request):
data = json.loads(request.body)
post_id = data['post_id']
comment_body = data['body']
post = get_object_or_404(Post, id=post_id)
comment = Comment.objects.create(
user=request.user,
body=comment_body,
post=post
)
created_at = date_format(comment.created_at, format='M. d, Y, h:m a', use_l10n=True)
return JsonResponse({
'user': comment.user.username,
'body': comment.body,
'created_at': created_at
})
In this first template part we just display data, note that {# % if not has_commented % #}
is commented out, when first writing the code I limited the number comments to one per User
.
post_detail.html (HTML & DTL):
{% extends 'base.html' %}
{% block content %}
<h1>{{post.title}}</h1>
<h4>{{post.created_at}}</h4>
<p>{{post.body}}</p>
<hr>
<div id="comment-div">
{# % if not has_commented % #}
<form action="{% url 'love:comment-create' %}" id="comment-form">
<input type="hidden" id="post-id" value="{{post.id}}">
<textarea id="comment-body" maxlength="255" rows="4" cols="50"></textarea>
<br>
<button type="submit" id="submit-comment">Comment</button>
</form>
<hr>
{# % endif % #}
</div>
<div id="comment-list">
<h2> Comments </h2>
{% if post.comments.all %}
{% for comment in post.comments.all %}
<p>At {{comment.created_at|date:"M. d, Y, h:m a"}} {{comment.user}} commented:</p>
<p>{{comment.body}}</p>
{% endfor %}
{% endif %}
</div>
This second part, containing the <script>
is responsible for every event in the template after adding a new comment. The first function is used to retrieve csrftoken value from cookies.
Then, on submit
click we collect data and send the AJAX request. Attention to a few lines. First, where csrf header name is set headers: {'X-CsrfToken': csrftoken}
. Second, where data is converted to JSON string data: JSON.stringify({ post_id: post_id, body: body })
.
post_detail.html (JS):
<script>
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i ) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length 1) === (name '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length 1));
break;
}
}
}
return cookieValue;
}
$('#submit-comment').click( function (e) {
e.preventDefault();
var url = $('#comment-form').attr("action");
var post_id = $('#post-id').attr("value");
var body = $('#comment-body').val();
const csrftoken = getCookie('csrftoken');
$.ajax({
method: "POST",
url: url,
headers: {'X-CsrfToken': csrftoken},
data: JSON.stringify({ post_id: post_id, body: body }),
success: function(comment) {
// $('#comment-div').prop("hidden", true);
$('#comment-body').val('');
$('#comment-list').append(
`<p>At ${comment.created_at} ${comment.user} commented:</p>
<p>${comment.body}</p>`
);
}
});
});
</script>
{% endblock %}
Lastly, on success, clear body input and update the template by appending data to <div id="comment-list">
. To limit to one comment per user, uncomment $('#comment-div').prop("hidden", true);