I am trying to post comments to a post in Django, but my form is never valid. I have discovered that this is because my request.method is always a GET and not a POST. I would like to allow a user to add a new comment and have it automatically save to the database and display on my post's detail view.
views.py
def add_comment_to_post(request, pk):
print("REQUEST METHOD", request.method)
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
print("POST")
print(request.POST)
form = CommentForm(request.POST)
if form.is_valid():
print("HREE")
comment = form.save(commit=False)
comment.author = request.user
comment.date_posted = timezone.now()
comment.post = post
comment.save()
return redirect('post_detail', pk=comment.post.pk)
else:
print("ELSE")
form = CommentForm()
# Note the indentation (this code will be executed both if NOT POST
# and if form is not valid.
return render(request, 'main/add_comment_to_post.html', {'form': form, 'post':post})
forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['author', 'content']
models.py
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
comment_id = models.AutoField(primary_key=True)
author = models.ForeignKey(Profile, on_delete=models.CASCADE)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
# approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
class Meta:
ordering = ['date_posted']
def __str__(self):
return 'Comment {} by {}'.format(self.content, self.author.first_name)
class Post(models.Model):
run_id = models.AutoField(primary_key=True)
author = models.ForeignKey(Profile, on_delete=models.CASCADE)
title = models.TextField(max_length=100, blank=False, default="title holder.")
distance = models.FloatField(default=0.0, blank=False)
time = models.IntegerField(default=0, blank=False)
date_posted = models.DateTimeField(default=timezone.now)
location = models.TextField(max_length=100, blank=False, default="")
image = models.TextField(max_length=250, blank=True)
content = models.TextField(max_length=1000, blank=True, default="")
#property
def pace(self):
if self.distance == 0.0:
return 0.0
return round(self.time / self.distance, 2)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
urls.py
urlpatterns = [
# path('', views.home, name="main-home"),
path('', PostListView.as_view(), name="main-home"),
path('admin/', admin.site.urls),
path('register/', views.register, name="register"),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
path('post/new/', PostCreateView.as_view(), name='post-create'),
path('post/<int:pk>/update/', PostUpdateView.as_view(), name='post-update'),
path('post/<int:pk>/delete/', PostDeleteView.as_view(), name='post-delete'),
path('about/', views.about, name="main-about"),
path('search/', SearchResultsView.as_view(), name="main-search"),
path('post/<int:pk>/comment/', views.add_comment_to_post, name='add_comment_to_post'),
]
add_comment_to_post.html
{% extends "main/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<h1>New comment</h1>
<form action="" method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">New Comment</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<a class="btn btn-outline-info" type="submit" href="{% url 'post-detail' pk=post.pk %}">Send</a>
</div>
</form>
{% endblock %}
What I have tried
checking out what type of request it is and handling it
searching other similar posts to no avail
changing the views function
Any help would be appreciated.
You didn't define else state for if form.is_valid(): statement. So, if your form has errors, view render your template again.
<div class="form-group">
<a class="btn btn-outline-info" type="submit" href="{% url 'post-detail' pk=post.pk %}">Send</a>
</div>
Your problem is this is a link and not actually a form submit. Try replacing that link with a simple <input type="submit" value="Send">.
Related
I'm working on a Django blog and I'm stuck here... I want to post comment and when I do that it just refresh the page and nothing happens.
I have no idea where I'm making a mistake, please see my code below:
this is views.py
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
related_posts = post.tags.similar_objects()[:3]
comments = post.comments.filter(active=True)
new_comment = None
if request.method == 'POST':
if request.user.is_authenticated:
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.post = post
new_comment.author = request.user
new_comment.save()
else:
messages.warning(request, 'You need to be logged in to add a comment.')
else:
if request.user.is_authenticated:
comment_form = CommentForm(initial={'author': request.user})
else:
comment_form = CommentForm()
context = {'post': post, 'related_posts': related_posts, 'comments': comments, 'new_comment': new_comment, 'comment_form': comment_form}
return render(request, 'post_detail.html', context)
this is comment part post_detail.html
{% if user.is_authenticated %}
<!--comment form-->
<div class="comment-form">
<h3 class="mb-30">Leave a Reply</h3>
<form class="form-contact comment_form" action="{% url 'post_detail' post.slug %}" method="post">
{% csrf_token %}
<div class="row">
<div class="col-12">
<div class="form-group">
{{ comment_form | crispy }}
</div>
</div>
</div>
<div class="form-group">
<button type="submit" class="button button-contactForm">Post Comment</button>
</div>
</form>
</div>
{% else %}
<div class="alert alert-danger" role="alert">
Please log in to post a comment.
</div>
{% endif %}
this is models.py
class Post(models.Model):
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created at")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Updated at")
published_at = models.DateTimeField(null=True, blank=True, editable=False, verbose_name="Published at")
post_title = models.CharField(max_length=200, verbose_name="Title")
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey('auth.User', verbose_name="Author", on_delete=models.CASCADE)
category = models.ForeignKey(Category, verbose_name="Category", on_delete=models.CASCADE)
tags = TaggableManager()
body = QuillField()
image = StdImageField(upload_to='featured_image/%Y/%m/%d/', variations={'standard':(1170,820),'banner':(1170,530),'single_post':(1170,556),'thumbnail':(500,500)})
image_credit = models.CharField(max_length=200, verbose_name="Image credit:")
status = models.IntegerField(choices=STATUS, default=0)
def get_absolute_url(self):
return reverse('post_detail', args=[self.slug])
def get_readtime(self):
result = readtime.of_text(self.body)
return result.text
class Meta:
verbose_name = "Post"
verbose_name_plural = "Posts"
ordering = ['-created_at']
def publish(self):
self.is_published = True
self.published_at = timezone.now()
self.save()
def __str__(self):
return self.post_title
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
body = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=False)
class Meta:
ordering = ['created_on']
def __str__(self):
return 'Comment "{}" by {}'.format(self.body, self.author.username)
this is forms.py
class CommentForm(forms.ModelForm):
author = forms.CharField(widget=forms.HiddenInput)
class Meta:
model = Comment
fields = ('body', 'user')
Please not that I can post comment over admin page (http://127.0.0.1:8000/admin), but on the front-end doesn't work for some reason.
Any ideas?
You need to redirect() after dealing with POST data, the tip is not specific to Django, its a good web practice in general so:
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
related_posts = post.tags.similar_objects()[:3]
comments = post.comments.filter(active=True)
new_comment = None
if request.method == 'POST':
if request.user.is_authenticated:
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.post = post
new_comment.author = request.user
new_comment.save()
return redirect(post.get_absolute_url()) # redirect here
else:
messages.warning(request, 'You need to be logged in to add a comment.')
else:
if request.user.is_authenticated:
comment_form = CommentForm(initial={'author': request.user})
else:
comment_form = CommentForm()
context = {'post': post, 'related_posts': related_posts, 'comments': comments, 'new_comment': new_comment, 'comment_form': comment_form}
return render(request, 'post_detail.html', context)
I'm working on a blog and I was following instructions but now I don't understand why I have problem with pk.
This is my views.py
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from .models import Category, Post
def post_list(request):
posts = Post.objects.filter(created_at__lte=timezone.now()).order_by('created_at')
latest_posts = Post.objects.filter(created_at__lte=timezone.now()).order_by('created_at')[:9]
context = {'posts': posts, 'latest_posts': latest_posts}
return render(request, 'home.html', context)
def post_detail(request, post, pk):
latest_posts = Post.objects.filter(created_at__lte=timezone.now()).order_by('created_at')[:9]
post = get_object_or_404(Post, pk=pk)
context = {'post': post, 'latest_posts': latest_posts}
return render(request, 'post_detail.html', context)
This is blog/urls.py
urlpatterns = [
path('', views.post_list, name='home'),
path('<slug:post>/', views.post_detail, name='post_detail'),
]
This is models.py
class Post(models.Model):
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created at")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Updated at")
is_published = models.BooleanField(default=False, verbose_name="Is published?")
published_at = models.DateTimeField(null=True, blank=True, editable=False, verbose_name="Published at")
title = models.CharField(max_length=200, verbose_name="Title")
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey('auth.User', verbose_name="Author", on_delete=models.CASCADE)
category = models.ForeignKey(Category, verbose_name="Category", on_delete=models.CASCADE)
body = RichTextField(blank=True, null=True)
image = StdImageField(upload_to='featured_image/%Y/%m/%d/', variations={'standard':(1170,820),'banner':(1170,530),'thumbnail':(500,500)})
status = models.IntegerField(choices=STATUS, default=0)
def get_absolute_url(self):
return reverse('post_detail', args=[self.slug])
class Meta:
verbose_name = "Post"
verbose_name_plural = "Posts"
ordering = ['-created_at']
def publish(self):
self.is_published = True
self.published_at = timezone.now()
self.save()
def __str__(self):
return self.title
Also, I'm adding a list of post, it might be connected with get_absolute_url
{% extends "base.html" %}
{% load static %}
{% block content %}
<!-- Main Wrap Start -->
<main class="position-relative">
<div class="post-carausel-1-items mb-50">
{% for post in latest_posts %}
<div class="col">
<div class="slider-single bg-white p-10 border-radius-15">
<div class="img-hover-scale border-radius-10">
<!--<span class="top-right-icon bg-dark"><i class="mdi mdi-flash-on"></i></span>-->
<a href="{{ post.get_absolute_url }}">
<img class="border-radius-10" src="{{ post.image.standard.url }}" alt="post-slider">
</a>
</div>
<h6 class="post-title pr-5 pl-5 mb-10 mt-15 text-limit-2-row">
{{ post.title }}
</h6>
<div class="entry-meta meta-1 font-x-small color-grey float-left text-uppercase pl-5 pb-15">
<span class="post-by">By {{ post.author }}</span>
<span class="post-on">{{ post.created_at}}</span>
</div>
</div>
</div>
{% endfor %}
</div>
</main>
{% endblock content%}
I have an error...
post_detail() missing 1 required positional argument: 'pk'
Everything was working until I was started working on post detail. Any ideas why this is happening?
You seem to be confused with what the primary key for your model is. The primary key is the auto-generated id (pk) but according to your urls.py you want to use slug for fetching records.
First, change your path to:
path('<slug:slug>/', views.post_detail, name='post_detail'),
then your view signature to:
def post_detail(request, slug):
and finally, fetch your record using:
post = get_object_or_404(Post, slug=slug)
I want to make reply of comment function. so I added parent option in Answer model. answer_create() working well.
but, when i submit reply of comment, url is different with url i setting.
i think i should be http://127.0.0.1:8000/debateboard/305/
but return http://127.0.0.1:8000/debateboard/answer/reply/31/
help me plz
models.py
class Question(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author_question')
subject = models.CharField(max_length=200)
content = models.TextField()
create_date = models.DateTimeField()
modify_date = models.DateTimeField(null=True, blank=True)
voter = models.ManyToManyField(User, related_name='voter_question')
def __str__(self):
return self.subject
class Answer(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author_answer')
question = models.ForeignKey(Question, on_delete=models.CASCADE)
content = models.TextField()
create_date = models.DateTimeField()
modify_date = models.DateTimeField(null=True, blank=True)
voter = models.ManyToManyField(User, related_name='voter_answer')
######### i added this parent to make reply of comment
parent = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True, related_name='+')
def __str__(self):
return self.content
#property
def children(self):
return Answer.objects.filter(parent=self).order_by('-create_date').all()
#property
def if_parent(self):
if self.parent is None:
return True
return False
urls.py
urlpatterns = [
path('', base_views.index, name = 'index'),
path('<int:question_id>/', base_views.detail, name = 'detail'),
path('answer/create/<int:question_id>/', answer_views.answer_create, name = 'answer_create'),
path('answer/reply/<int:answer_id>/', answer_views.reply_comment_create , name='reply_comment_create'),
view
def detail(request, question_id):
# question = Question.objects.get(id = question_id)
question = get_object_or_404(Question, pk = question_id)
context = {'question' : question}
return render(request, 'debateboard/detail.html', context)
#login_required(login_url='common:login')
def reply_comment_create(request, answer_id):
answer = get_object_or_404(Answer, pk=answer_id)
# question = get_object_or_404(Question, pk = answer.question.id)
if request.method == "POST":
form = AnswerForm(request.POST)
if form.is_valid():
sub_answer = form.save(commit=False)
sub_answer.author = request.user
sub_answer.create_date = timezone.now()
sub_answer.parent = answer.id
sub_answer.save()
# return redirect('{}#answer_{}'.format(resolve_url('debateboard:detail', question_id = answer.question.id), answer.id))
return redirect('debateboard:detail', question_id = answer.question.id)
else:
return HttpResponseNotAllowed('only post is possible')
context = {'question' : answer.question, 'form': form}
return render(request, 'debateboard/detail.html', context)
#login_required(login_url = 'common:login')
def answer_create(request, question_id):
question = get_object_or_404(Question, pk = question_id)
# question.answer_set.create(content=request.POST.get('content'), create_date = timezone.now())
if request.method == 'POST':
form = AnswerForm(request.POST)
if form.is_valid():
answer = form.save(commit=False)
answer.author = request.user
answer.create_date = timezone.now()
answer.question = question
answer.save()
# return redirect('debateboard:detail', question_id = question.id)
return redirect('{}#answer_{}'.format(resolve_url('debateboard:detail', question_id = question.id), answer.id))
else:
return HttpResponseNotAllowed('Only POST is possible.')
context = {'question': question, 'form': form}
return render(request, 'debateboard/detail.html', context)
html
<!-- for reply of comment -->
<form action="{% url 'debateboard:reply_comment_create' answer.id %}" method="POST">
{% csrf_token %}
<div class="form-group">
<textarea name="reply_comment_{{ answer.id }}" id="reply_comment_{{ answer.id }}" cols="20" rows="5"></textarea>
<button type="submit">dd</button>
</div>
</form>
<!-- for comment-->
<form action="{% url 'debateboard:answer_create' question.id %}" method="post">
{% csrf_token %}
<div class="form-group">
<textarea {% if not user.is_authenticated %}disabled placeholder="로그인후 이용가능" {% endif %} name="content" id="content" cols="30" rows="15"></textarea>
</div>
<input {% if not user.is_authenticated %}disabled placeholder="로그인후 이용가능" {% endif %} type="submit" class="btn btn-primary" value="답변등록">
</form>
The address is structured this way because you add an object of class Answer. Anyway, look at how your url is constructed.
path('answer/reply/<int:answer_id>/', answer_views.reply_comment_create , name='reply_comment_create'),
If you want, check the SlugField which can be useful for create custom url.
I am working on my project I faced a problem with "COMMENT":
I add a comment as a section when the user clicks "view" button to see the post from the home page.
Django error :
NoReverseMatch at /Post/8
Reverse for 'comments' with arguments '('',)' not found. 1 pattern(s) tried: ['Post/(?P[0-9]+)$']
views.py file
def viewList(request, id):
item = Post.objects.get(id=id)
context = {
'item': item,
'comment_form': comment(),
'comments': item.get_comments.all(),
}
return render(request, 'auctions/item.html', context)
#login_required
def comments(request, id):
listing = Post.objects.get(id=id)
form = comment(request.PSOT)
newComment = form.save(commit=False)
newComment.user = request.user
newComment.listing = listing
newComment.save()
return HttpResponseRedirect(reverse("listing", {'id': id}))
models.py file
class Post(models.Model):
# data fields
title = models.CharField(max_length=64)
textarea = models.TextField()
# bid
price = models.FloatField(default=0)
currentBid = models.FloatField(blank=True, null=True)
imageurl = models.CharField(max_length=255, null=True, blank=True)
category = models.ForeignKey(
Category, on_delete=models.CASCADE, default="No Category Yet!", null=True, blank=True)
creator = models.ForeignKey(User, on_delete=models.PROTECT)
watchers = models.ManyToManyField(
User, blank=True, related_name='watched_list')
date = models.DateTimeField(auto_now_add=True)
# for activated the Category
activate = models.BooleanField(default=True)
def __str__(self):
return f"{self.title} | {self.textarea} | {self.date.strftime('%B %d %Y')}"
class Comment(models.Model):
body = models.CharField(max_length=100)
createdDate = models.DateTimeField(default=timezone.now)
# link to the post model
auction = models.ForeignKey(
Post, on_delete=models.CASCADE, related_name="get_comments")
user = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.BooleanField(default=True)
def __str__(self):
return self.createdDate.strftime('%B %d %Y')
HTML file
<!-- Comments -->
<div class="comments">
<p>Add a comment:</p>
<div class="row">
<div class="col-6">
<form action="{% url 'comments' listing.id %}" method="post">
{% csrf_token %}
<div class="input-group">
{{ comment_form }}
</div>
<input type="submit" value="save" class="btn btn-outline-dark btn-sm m-1"/>
</form>
</div>
{% for comment in comments %}
<div class="col-4">
Comments:
<h4>Name: {{comment.user}}</h4>
<h4>Content: {{comment.body}}</h4>
<h4>Date: {{comment.createdDate}}</h4>
</div>
{% endfor %}
</div>
</div>
url.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="index"),
path("login", views.login_view, name="login"),
path("logout", views.logout_view, name="logout"),
path("register", views.register, name="register"),
path("category/", views.category, name="category"),
path("Post", views.createList, name="createList"),
#comment url
path("Post/<str:id>", views.viewList, name="viewList"),
path("Post/<int:id>", views.comments, name="comments"),
]
You passed the Post object as item to the template, hence you should resolve the URL with:
<!-- use item not listing ↓ -->
<form action="{% url 'comments' item.id %}" method="post">
…
</form>
Your view to create comments also has some mistakes: you should use request.POST, not request.PSOT:
from django.shortcuts import redirect
#login_required
def comments(request, id):
if request.method == 'POST'
form = comment(request.POST, request.FILES)
if form.is_valid():
form.instance.user = request.user
form.instance.auction_id = id
form.save()
return redirect('viewList', id=id)
Furthermore your comment form should inherit from ModelForm and not Form:
class comment(forms.ModelForm):
class Meta:
model = Comment
fields = ('content',)
widgets = {
'content': forms.Textarea(attrs={"class": "form-control"})
}
finally your urls.py has two (fully) overlapping patterns, you should use a different URL to add the given comment:
urlpatterns = [
# …
#comment url
path('Post/<int:id>', views.viewList, name='viewList'),
path('Post/<int:id>/add', views.comments, name='comments'),
]
Note: Usually a Form or a ModelForm ends with a …Form suffix,
to avoid collisions with the name of the model, and to make it clear that we are
working with a form. Therefore it might be better to use CommentForm instead of
comment.
I cannot Save comments in django
my post model is
#model.py
class Post(models.Model):
thumbnail = models.ImageField()
slug = models.SlugField(null=True)
title = models.CharField(max_length=200)
description = models.TextField()
content = HTMLField(null=True)
categories = models.ManyToManyField(Category)
featured = models.BooleanField()
timeStamp = models.DateTimeField(auto_now_add=True)
commentCount = models.IntegerField(default=0)
viewCount = models.IntegerField(default=0)
def __str__(self):
return self.title
def absoluteUrl(self):
return reverse('postContent', kwargs={
'slug' : self.slug
})
#property
def getComments(self):
return self.comments.all()
my comment model is
#model.py
class Comment(models.Model):
name = models.CharField(max_length=25)
timeStamp = models.DateTimeField(auto_now_add=True)
comment = models.TextField()
post = models.ForeignKey(Post, related_name='comments', on_delete=models.CASCADE)
def __str__(self):
return self.name
my forms.py is
from .models import Post, Comment
class CommentForm(forms.ModelForm):
name = forms.CharField(widget=forms.TextInput(attrs={
'class': 'form-control',
'id' : 'name',
'placeholder': 'Name'
}))
comment = forms.CharField(widget=forms.Textarea(attrs={
'class' : 'form-control w-100',
'id' : 'comment',
'cols' : '30',
'rows' : 9,
'placeholder' : 'Write Comment'
}))
class Meta:
model = Comment
fields = ('name', 'comment')
my view.py is
from .forms import CommentForm
def post(request, slug):
categoryCount = getCategoryCount()
featured = Post.objects.filter(featured=True)
post = get_object_or_404(Post, slug=slug)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
form.instance.post = post
form.save()
else:
form = CommentForm()
context = {
'post' : post,
'featured': featured,
'categoryCount': categoryCount,
'form' : form
}
return render(request, 'post.html', context)
my post.html is
<div class="comment-form">
<h4>Leave a Reply</h4>
<form class="form-contact comment_form" method="POST" action="." id="commentForm">
{% csrf_token %}
<div class="row">
<div class="col-12">
<div class="form-group">
{{ form }}
</div>
</div>
</div>
<div class="form-group">
<button type="submit" class="button button-contactForm btn_1 boxed-btn">Send Message</button>
</div>
</form>
</div>
my urls.py
from Posts.views import posts, post, search
urlpatterns = [
path('admin/', admin.site.urls),
path('posts/', posts, name='postLists'),
path('posts/<slug>', post, name='postContent'),
path('search/', search, name='search'),
path('tinymce', include('tinymce.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
I can save comments in admin section but the main problem is when I click the submit button in html the comment is not saving. I am new to django and I cannont figure out the problem.
Is I miss something else?
You can do something like this
temp = form.save(commit=False)
temp.post = post
temp.save()
Update
as per #Abdul Aziz Barkat mentioned you have to change your URL in urls.py add a trailing slash after posts/<slug> and add one more URL to handle POST request
for eg.
from Posts.views import posts, post, search
urlpatterns = [
path('posts/<slug>/', post, name='postContent'),
path('post_comment/<slug>/', post, name='postComment'),
]
and than inside your form action add this
<form method="POST" action="{% url 'postComment' post.slug %}">
and in your view.py
def post(request, slug):