Django Ajax div not reloading/disappearing after posting comment - python

I am trying to resolve the problem for few days. I want to post the comment without refreshing page. The problem is the div in which comments are is not reloading or disappearing after hitting post button. It depends on how i "catch" it: when I use the id like:
$('#comments').append('<p>' + data.comment.content + '</p><small>' + data.comment.author + '/' + data.comment.date_of_create + '</small>');
it is not reloading and if i use class it disappears. When i refresh the page, comment is being add properly. This is my code below.
views.py
class PostDetailView(FormMixin, DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
form_class = CommentForm
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['form'] = CommentForm(initial={'post': self.object})
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
if request.method == 'POST':
new_comment = Comment.objects.create(
author = self.request.user,
post = self.object,
content = form.cleaned_data['content']
)
return JsonResponse({'comment': model_to_dict(new_comment)}, status=200)
else:
return self.form_invalid(form)
def form_valid(self, form):
form.instance.author = self.request.user
form.instance.post = self.get_object()
form.save()
return super(PostDetailView, self).form_valid(form)
def get_success_url(self):
return reverse('post_detail', kwargs={'slug': self.object.slug})
comments.js
$(document).ready(function(){
$("#commentBtn").click(function(){
var serializedData = $("#commentForm").serialize();
$.ajax({
url: $("commentForm").data('url'),
data: serializedData,
type: 'post',
dataType: 'json',
success: function(data){
console.log(data);
$('#comments').append('<p>' + data.comment.content + '</p><small>' + data.comment.author + '/' + data.comment.date_of_create + '</small>');
$('textarea').val('');
}
})
});
});
post_detail.html
<h3>Dodaj komentarz:</h3>
<form action="" method="POST" id="commentForm" data-url="{% url 'post_detail' slug=post.slug %}">
{% csrf_token %}
{{ form.as_p }}
<button type="button" id="commentBtn">Wyƛlij</button>
</form>
<div id="comments">
<h3>Komentarze:</h3>
{% for comment in post.comment_set.all %}
<p>{{ comment.content }}</p>
<small>{{ comment.author }} / {{ comment.date_of_create }}</small>
{% endfor %}
</div>
In my base.html file i placed on the bottom:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="{% static 'js/comment.js' %}"></script>
settings.py
STATIC_URL = '/static/'
STATICFILES_DIR = [
os.path.join(BASE_DIR, 'static')
]
The most annoying thing is I followed some resolutions/tutorials, i do the same and it still not working. Can it be problem with bad configure? Thanks for any help in advance!
console gives object:
https://i.stack.imgur.com/vWp9N.png

Instead of this
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
do this
<script src="https://code.jquery.com/jquery-3.5.1.min.js" ></script>.

Related

The ability to like/vote posts from the post list

I have a problem. I made likes to the posts on the video from YouTube. It is possible to like a post only from post_detail view and it's works correctly! How to make it possible to like posts in the post_list (in my case it's 'feed')? Please help!
MY CODE:
utils.py
def is_ajax(request):
return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
views.py
from .utils import is_ajax
def feed(request):
comments = Comment.objects.all()
posts = Post.objects.all().order_by('-created_at')
return render(request, 'main/feed.html', {'posts': posts,
'comments': comments})
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
comments = Comment.objects.filter(post=post, reply=None).order_by('-created_at')
is_voted = False
if post.votes.filter(id=request.user.id).exists():
is_voted = True
if request.method == 'POST':
comment_form = CommentForm(request.POST or None)
if comment_form.is_valid():
content = request.POST.get('content')
reply_id = request.POST.get('comment_id')
comment_qs = None
if reply_id:
comment_qs = Comment.objects.get(id=reply_id)
comment = Comment.objects.create(
post = post,
author = request.user,
content = content,
reply = comment_qs
)
comment.save()
return redirect(post.get_absolute_url())
else:
for error in list(comment_form.errors.items()):
messages.error(request, error)
else:
comment_form = CommentForm()
return render(request, 'main/post_detail.html', {'post':post,
'comment_form':comment_form,
'comments':comments,
'is_voted':is_voted})
#login_required
def post_vote(request, slug):
post = get_object_or_404(Post, id=request.POST.get('id'), slug=slug)
is_voted = False
if post.votes.filter(id=request.user.id).exists():
post.votes.remove(request.user)
is_voted = False
else:
post.votes.add(request.user)
is_voted = True
context = {
'post': post,
'is_voted': is_voted,
}
if is_ajax(request):
html = render_to_string('main/votes.html', context, request)
return JsonResponse({'form':html})
votes.html
<form action="{% url 'main:post-vote' slug=post.slug %}" method="post">
{% csrf_token %}
{% if is_voted %}
<button type="submit" id="vote" name="post_id" value="{{ post.id }}" btn="btn btn-outline">Unvote</button>
{% else %}
<button type="submit" id="vote" name="post_id" value="{{ post.id }}" btn="btn btn-outline">Vote</button>
{% endif %}
</form>
{{post.votes_count}}
post_detail.html
<div id="vote-section">
{% include 'main/votes.html' %}
</div>
<script>
$(document).ready(function(event){
$(document).on('click','#vote', function(event){
event.preventDefault();
var pk = $(this).attr('value');
$.ajax({
type: 'POST',
url: "{% url 'main:post-vote' slug=post.slug %}",
data: {'id':pk, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
dataType: 'json',
success: function(resoponse){
$('#vote-section').html(resoponse['form'])
console.log($('#vote-section').html(resoponse['form']));
},
error: function(rs, e){
console.log(rs.responseText);
}
})
});
});
</script>
feed.html
...
{% for post in posts %}
<div id="vote-section">
{% include 'main/votes.html' %}
</div>
...
{% endfor %}
<script>
$(document).ready(function(event){
$(document).on('click','#vote', function(event){
event.preventDefault();
var pk = $(this).attr('value');
$.ajax({
type: 'POST',
url: "{% url 'main:post-vote' slug=post.slug %}",
data: {'id':pk, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
dataType: 'json',
success: function(resoponse){
$('#vote-section').html(resoponse['form'])
console.log($('#vote-section').html(resoponse['form']));
},
error: function(rs, e){
console.log(rs.responseText);
}
})
});
});
</script>
models.py
class Post(models.Model):
...
votes = models.ManyToManyField(get_user_model(), related_name='votes', blank=True)
#property
def votes_count(self):
return self.votes.count()
urls.py
urlpatterns = [
path('', views.feed, name='feed'),
path('post_create/', views.post_create, name='post-create'),
path('post/<slug>/', views.post_detail, name='post-detail'),
path('post/<slug>/delete', views.post_delete, name='post-delete'),
path('post/<slug>/update', views.post_update, name='post-update'),
path('post/<slug>/vote', views.post_vote, name='post-vote'),
path('comment/<slug>/delete', views.comment_delete, name='comment-delete'),
path('comment/<slug>/update', views.comment_update, name='comment-update'),
path('comment_reply/<slug>/delete', views.reply_comment_delete, name='comment-reply-delete')
]
I understand that I need to do the same as in post detail. I tried to do this, but i got errors
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
comments = Comment.objects.filter(post=post, reply=None).order_by('-created_at')
is_voted = False
if post.votes.filter(id=request.user.id).exists():
is_voted = True
...
Tried to do:
def feed(request, slug=None):
comments = Comment.objects.all()
posts = Post.objects.all().order_by('-created_at')
v_post = get_object_or_404(Post, slug=slug)
is_voted = False
if v_post.votes.filter(id=request.user.id).exists():
is_voted = True
return render(request, 'main/feed.html', {'posts': posts,
'comments': comments,
'is_voted': is_voted})
got
Page not found (404)
Also tried
def feed(request, slug):
comments = Comment.objects.all()
count_filter = Q(votes=request.user)
vote_case = Count('votes', filter=count_filter, output_field=BooleanField())
posts = Post.objects \
.annotate(is_voted=vote_case) \
.all().order_by('-created_at')
return render(request, 'main/feed.html', {'posts': posts,
'comments': comments})
got
TypeError at /
feed() missing 1 required positional argument: 'slug'
UPD: For the attemp above, I also did something:
set slug to None
def feed(request, slug=None):
And and moved the script to the for-loop (for post in posts)
This gave its results. Now everything works almost as it should, only there is a bug. When you click on the vote button, he votes for for the post below, and vice versa
OK - there's a fair bit to unpack here:
First, generally speaking, it looks you you want Ajax to handle the bulk of the work. The reason (or at least the initial reason) why that isn't working above is in how you replicate forms
For well constructed HTML, only one element per page should have a particular ID
Your form is generated like this
{% for post in posts %}
<div id="vote-section"> #This will repeat for every post
{% include 'main/votes.html' %}
</div>
What this means is that every form will have the same id, vote-section, so
success: function(resoponse){
$('#vote-section').html(resoponse['form'])
won't know where to write.
Similarly, in the forms on list, every button has id="vote"
This isn't an issue on the details page, because there's only one form and one vote/unvote button. On the list page, with multiple forms, it is.
So what to do about it?
First off, add a class to your button.
votes.html
<button type="submit" id="vote" class="vote-button" name="post_id" value="{{ post.id }}" btn="btn btn-outline">Unvote</button>
Next tweak your feed html so it uses IDs correctly. We'll use {{[forloop.counter][1]}} to generate unique IDs.
We'll also move the ajax into the loop, as the slug for each post is different in the url: generation section. We add the new div ID into the selector and the success function to make it unique.
feed.html
{% for post in posts %}
<div id="vote-section{{forloop.counter}}">
{% include 'main/votes.html' %}
</div>
...
<script>
$(document).ready(function(event){
$(document).on('click','#vote-section{{forloop.counter}} .vote-button', function(event){
event.preventDefault();
var pk = $(this).attr('value');
$.ajax({
type: 'POST',
//this needs to be in the for post in posts loop to get post.slug
url: "{% url 'main:post-vote' slug=post.slug %}",
data: {'id':pk, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
dataType: 'json',
success: function(resoponse){
$('#vote-section{{forloop.counter').html(resoponse['form'])
console.log($('#vote-section{{forloop.counter}}').html(resoponse['form']));
},
error: function(rs, e){
console.log(rs.responseText);
}
})
});
});
</script>
{% endfor %}
Now there are probably ways to make this more efficient, but hopefully it should get you over the hump to where it works.

how do I turn a function into a class and pass the form to a Django?

in my Django project i have a function:
def retail_product_detail(request, slug):
context = {
'product_retail': get_object_or_404(Product, slug=slug),
'cart_product_form': CartAddProductRetailForm(),
'related_products': Product.objects.filter(category__slug=self.kwargs['slug'])
}
return render(request, 'retail/single_product_retail.html', context)
how can i turn it into class RetailProductDetail and pass the form to be displayed?
i tried:
class RetailProductDetail(FormView):
template_name = 'retail/single_product_retail.html'
context_object_name = 'product_retail'
form_class = CartAddProductRetailForm
def get_queryset(self):
return Product.objects.filter(category__slug=self.kwargs['slug'])
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(**kwargs)
context['product_retail'] = Product.objects.get(slug=self.kwargs['slug'])
context['related_products_1'] = Product.objects.select_related('tag').all()
return context
But form doesn't works.
In template:
<form action="{% url 'cart_add_retail' slug=product_retail.slug %}" method="post">
{{ cart_product_form }}
{% csrf_token %}
<br>
<input class="btn btn-outline-dark flex-shrink-0" type="submit" value="Add to cart>
</form>
How can I fix it?
its because default context name for form is form in FormView CBV
try in template
{{ form }}
I was able to solve the problem in the following way:
Instead of:
form_class = CartAddProductRetailForm
I used:
def get_context_data(self, *, object_list=None, **kwargs):
context['cart_product_form'] = CartAddProductRetailForm

How to modify a form when returning it?

I am looking to modify a form from the user and return that different form in django, however I have tried many different ways, all in views.py, including:
Directly modifying it by doing str(form) += "modification"
returning a new form by newform = str(form) + "modification"
creating a different Post in models, but then I realized that wouldn't work because I only want one post
All the above have generated errors such as SyntaxError: can't assign to function call, TypeError: join() argument must be str or bytes, not 'HttpResponseRedirect', AttributeError: 'str' object has no attribute 'save', and another authority error that said I can't modify a form or something like that.
Here is a snippet from views.py:
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['content']
title = ['title'] #
template_name = 'blog/post_new.html'
success_url = '/'
def form_valid(self, form):
#debugging
tempvar = (str(form).split('required id="id_content">'))[1].split('</textarea></td>')[0] #url
r = requests.get(tempvar)
tree = fromstring(r.content)
title = tree.findtext('.//title')
print(title)
form.instance.author = self.request.user
if "http://" in str(form).lower() or "https://" in str(form).lower():
if tempvar.endswith(' '):
return super().form_valid(form)
elif " http" in tempvar:
return super().form_valid(form)
elif ' ' not in tempvar:
return super().form_valid(form)
else:
return None
models.py:
class Post(models.Model):
content = models.TextField(max_length=1000)
title = models.TextField(max_length=500, default='SOME STRING') #
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
likes= models.IntegerField(default=0)
dislikes= models.IntegerField(default=0)
def __str__(self):
return (self.content[:5], self.title[:5]) #
#property
def number_of_comments(self):
return Comment.objects.filter(post_connected=self).count()
And in home.html, where the post (along with the title and content) is supposed to be shown:
<a
style="color: rgba(255, 255, 255, 0.5) !important;"
href="{% url 'post-detail' post.id %}">
<p class="mb-4">
{{ post.content }}
{{ post.title }} #
</p>
</a>
The original template I'm modifying can be found here.
Thank you so much for your help, I will be very glad to take any advice!!
Ps: I'm using Python 3.7.4
Create a forms.py file inside the app you are talking about, it should look like this:
from django import forms
from . import models
class YourFormName(forms.ModelForm):
class Meta:
model = models.your_model_name
fields = ['field1', 'field2' ,...] # Here you write the fields of your model, this fields will appear on the form where user post data
Then you call that form into your views.py so Django can render it into your template, like this:
def your_view(request, *args, **kwargs):
if request.method == 'POST':
form = forms.YourFormName(request.POST, request.FILES)
if form.is_valid():
instance = form.save(commit=False)
instance.user= request.user
instance.save()
return redirect('template.html') # To redirect if the form is valid
else:
form = forms.YourFormName()
return render(request, "template.html", {'form': form}) # The template if the form is not valid
And the last thing to do is create the template.html:
{% extends 'base.html' %}
{% block content %}
<form action="{% url 'the_url_that_renders_this_template' %}" method='POST' enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
<button type="submit">Submit</button>
</form>
{% endblock content %}
If you want to take the data from DB submitted in that form, you do so with a new function in views.py:
def show_items(request, *args, **kwargs):
data = YourModelName.objects.all()
context = {
"data": data
}
return render(request, "show_items.html", context)
Then in show_items.html:
{% extends 'base.html' %}
{% block content %}
{% for item in data %}
{{item.field1}}
{{item.field2}}
...
{{The items you want to show in that template}}
{% enfor %}
{% endblock content %}
That's what you wanted to do? If not, add a further explanation on what actually you want to do

Django Like count is not incrementing

I added a like button to my site and it gets highlighted when clicked on it but does not increment the number of likes or shows the count in the admin. I am not understanding what is the mistake I have done. Please help me to solve this. My code is as follows.
models.py
class Post(models.Model):
post = models.TextField()
user = models.ForeignKey(User)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
likes = models.IntegerField(default=0)
def __str__(self):
return self.post
views.py
#login_required
def about(request,pk):
context = {}
template = 'about.html'
return render(request, template, context)
post = get_object_or_404(Post, pk=pk)
Post.objects.get(pk=pk)
post_id = post.pk
liked = False
if request.session.get('has_liked_' + str(post_id), liked):
liked = True
print("liked {}_{}".format(liked, post_id))
context = {'post': post, 'liked': liked}
return render(request, 'imagec/about.html', {'post': post})
#login_required()
def like_post(request):
liked = False
if request.method == 'GET':
post_id = request.GET['post_id']
post = Post.objects.get(id=int(post_id))
if request.session.get('has_liked_'+post_id, liked):
print("unlike")
if post.likes > 0:
likes = post.likes - 1
try:
del request.session['has_liked_'+post_id]
except KeyError:
print("keyerror")
else:
print("like")
request.session['has_liked_'+post_id] = True
likes = post.likes + 1
post.likes = likes
post.save()
return HttpResponse(likes, liked)
urls.py
url(r'like_post/$', imagec_views.like_post, name='like_post'),
about.html
<p>
<strong id="like_count">{{ post.likes }}</strong> people like this category
{% if user.is_authenticated %}
<input type="button" onclick="jQuery(this).toggleClass('active')" data-post_id="{{post.id}}" id="likes" value ='Like'>
{% endif %}
</p>
base.html
<script type="text/javascript" src="{% static 'js/blog-ajax.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery-3.js' %}"></script>
<script src="{% static 'js/jquery-3.js' %}"></script>
<script src="{% static 'js/blog-ajax.js' %}"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src = {% static "js/ajax-blog.js" %}> </script>
<script src = {% static "js/jquery-3.js" %}> </script>
ajax-blog.js
$('#likes').click(function(){
var postid;
postid= $(this).attr("data-post_id");
$.get('/imagec/like_post/', {post_id: postid}, function(data){
$('#like_count').html(data);
});
});
Thank You
You can debug it by using console.log in JS side and print in Python side.
ajax-blog.js
$('#likes').click(function(){
var postid;
postid= $(this).attr("data-post_id");
console.log("Post id to be send " + postid);
$.get('/imagec/like_post/', {post_id: postid}, function(data){
console.log("data received: "+data);
$('#like_count').html(data);
});
});
views.py
#login_required
def about(request,pk):
context = {}
template = 'about.html'
return render(request, template, context)
post = get_object_or_404(Post, pk=pk)
Post.objects.get(pk=pk)
post_id = post.pk
liked = False
if request.session.get('has_liked_' + str(post_id), liked):
liked = True
print("liked {}_{}".format(liked, post_id))
context = {'post': post, 'liked': liked}
return render(request, 'imagec/about.html', {'post': post})
#login_required()
def like_post(request):
liked = False
if request.method == 'GET':
post_id = request.GET['post_id']
post = Post.objects.get(id=int(post_id))
if request.session.get('has_liked_'+post_id, liked):
print("unlike")
if post.likes > 0:
likes = post.likes - 1
try:
del request.session['has_liked_'+post_id]
except KeyError:
print("keyerror")
else:
print("like")
request.session['has_liked_'+post_id] = True
likes = post.likes + 1
post.likes = likes
print("updated liked ", post.likes)
post.save()
return HttpResponse(likes, liked)
And check the console prints.
Other than that, there are two main suggestions from my end,
don't use get method to update a value in database
don't use sessions to save a flag that if a user liked a post. That logic might bug you
in the future.

Django- why after using ajax, it becomes an empty page

The function on the page:
user enter some information on the form
without refreshing, the right side can show some query result(sales) based on user entry
Before adding the ajax function, the page can display the form and the table. But after using the ajax, the page becomes empty.
Can anyone help check what is the reason? Thanks in advance.
url
url(r'^result_list/$',ResultView.as_view(),name='result'),
models.py
class Input(models.Model):
company=models.CharField(max_length=100)
region=models.CharField(max_length=100)
class Result(models.Model):
sales=models.IntegerField(blank=False,null=False)
views.py
from django.views.generic.list import ListView
from django.core import serializers
class ResultView(ListView):
context_object_name = 'result_list'
template_name = 'result_list.html'
def get_queryset(self):
return Result.objects.all()
def post(self, request, *args, **kwargs):
form = InputForm(request.POST)
if form.is_valid():
if self.request.is_ajax():
company = form.cleaned_data['company']
region = form.cleaned_data['region']
queryset=Result.objects.filter(region=region).aggregate(Sum('sales'))
return HttpResponse(json.dumps(queryset))
else:
return HttpResponse(form.errors)
'''def get_context_data(self, **kwargs):
context = super(ResultView, self).get_context_data(**kwargs)
context["sales"] = self.get_queryset().aggregate(Sum('sales'))'''
html
<style>...CSS part
</style>
<script src="http://apps.bdimg.com/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.js"></script>
<script src="http://malsup.github.com/jquery.form.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#InputForm").submit(function() { // catch the form's submit event
var region= $("#id_region").val();
var company= $("#id_company").val();
$.ajax({
data: $(this).serialize(), // get the form data
type: $(this).attr('post'),
dataType: 'json',
url: "dupont_list/",
success: function(data) {
var html = "<table>"
html += "<td>"+data['sales__sum']+"</td>"
html += "</table>"
$("#result").html(html);
html += "</table>"
$("#result").html(html);
}
return false;
});
})
</script>
<form id="InputForm" method="post" action=""> #here is the data entry form
{% csrf_token %}
<!--enter the company name-->
<div class="field">
{{ form.company.errors }}
<label id="id_company" name="company" for="{{ form.company.id_for_label }}">Company:</label>
{{ form.company }}
</div>
<!--select region-->
<div class="field" >
<label> Select the Region:
{{ form.region }}
{% for region in form.region.choices %}
<option value="region" name= "region" id="id_region">{{region}} </option>
{% endfor %}
</label>
</div>
<!--submit-->
<p><input type="button" value="Submit" /></p></div>
</form>
</div>
<div id="result" class="result"> <!--Showing the filtered result in database-->
<table>
<tr><b>Sales</b></tr>
<td> {{sales.sales__sum}}</td>
<tr><b>Employee</b></tr>
<td> {{employee.employee__sum}}</td>
</table>
I would go with a FormView:
class ResultView(FormView):
context_object_name = 'result_list'
template_name = 'result_list.html'
form_class = InputForm
def get_context_data(self, **kwargs):
context = super(ResultView, self).get_context_data(**kwargs)
context["results"] = Result.objects.all()
context["sales"] = context.results.aggregate(Sum('sales'))
return context
def form_valid(self,form):
company = form.cleaned_data['company']
region = form.cleaned_data['region']
queryset = Result.objects.filter(region=region).aggregate(Sum('sales'))
return HttpResponse(json.dumps(queryset))
This way you're bypassing the get_context_data and get_queryset methods if the form is valid and return a custom content response.

Categories

Resources