comment not being submitted by my django form - django

I want my users to be able to add comments under a transfernews [I am creating a sports related website], I tried this code, but for some reason I am getting this error TypeError at /transfernews/ Field 'id' expected a number but got <Transfernews: Transfernews object (3)>., I can add comments manually from the admin page but can't from the comment form. Can anyone please tell me how to fix my code?
My models.py:
class Transfernews(models.Model):
player_name = models.CharField(max_length=255)
player_image = models.CharField(max_length=2083)
player_description = models.CharField(max_length=3000)
date_posted = models.DateTimeField(default=timezone.now)
class Comment(models.Model):
user = models.ForeignKey(to=User, on_delete=models.CASCADE)
transfernews = models.ForeignKey(Transfernews, related_name="comments", on_delete=models.CASCADE)
body = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '%s - %s' % (self.transfernews.player_name, self.user.username)
My forms.py:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('body',)
My views.py:
def transfer_targets(request):
transfernews = Transfernews.objects.all()
news = request.POST.get("transfer_id", None)
form = CommentForm(request.POST or None)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.user = User.objects.get(id=request.user.id)
new_comment.transfernews_id = Transfernews.objects.get(id=news)
new_comment.save()
return redirect(request.path_info)
return render(request, 'transfernews.html', {'transfernews': transfernews, 'form': form})
My html file:
{% for transfer in transfernews %}
<h2>Comments...</h2>
{% if not transfer.comments.all %}
No comments Yet...
{% else %}
{% for comment in transfer.comments.all %}
<strong>
{{ comment.user.username }} - {{ comment.date_added }}
</strong>
<br/>
{{ comment.body }}
<br/><br/>
{% endfor %}
{% endif %}
<hr>
<div>Comment and let us know your thoughts</div>
<form method="POST">
{% csrf_token %}
<input type="hidden" value="{{ transfer.id}}">
<div class="bg-alert p-2">
<div class="d-flex flex-row align-items-start">
</div>
<div class="mt-2 text-right">
{{ form|crispy }}
<br>
<button class="btn btn-primary btn-sm shadow-none" type="submit">Post comment</button>
<button class="btn btn-outline-primary btn-sm ml-1 shadow-none" type="button">Cancel</button>
</div>
</div>
</form>
{% endfor %}

Try using new_comment.transfernews = transfernews instead of new_comment.transfernews_id = transfernews.id.
Also change redirect(request.path_info) to redirect(reverse('<app_name>:<url_namespace>')).

You have to add a name attribute to your hidden input, then only the hidden input data will be included in the form data.
<input type="hidden" name="transfer_id" value="{{ transfer.id}}">
and in your transfer_targets view make these changes
def transfer_targets(request):
transfernews = Transfernews.objects.all()
transfernews = request.POST.get("transfer_id", None)
form = CommentForm(request.POST or None)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.user = User.objects.get(id=request.user.id)
new_comment.transfernews_id = transfernews.id
new_comment.transfernews_id = Transfernews.objects.get(id=transfernews)
new_comment.save()
return redirect(request.path_info)
return render(request, 'transfernews.html', {'transfernews': transfernews, 'form': form})

Related

Why is the Django form not rendering in a class-based view?

Background: I've been working on the 'mini blog' challenge in Mozilla's tutorial. Got 'author' (user), 'post' working. Added 'comment'. With bits of code from this (among others), I managed to use a function-based view to get 'CommentForm' work on the 'post-detail' page. But trying to convert the function-based view to a class-based view stumped me. Because the various errors I encountered all relate to some key fundamental concepts, like explicit/implicit 'request', passing 'context', I've been working at it, hoping to understand Django better. But I'm getting nowhere. The problem is the form, 'CommentForm', doesn't render in the template. At one point, it rendered, but 'submit' the form led to not allowed error. I figured it related to 'GET' vs 'POST'. At this point, I'm not sure where the issue is--views? template? Appreciate some pointers.
Here's my code:
models.py
class Post(models.Model):
post = models.TextField(max_length=1000)
post_title = models.CharField(max_length=100)
description = models.TextField(max_length=500)
post_created_at = models.DateTimeField(auto_now_add=True)
post_updated_at = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, related_name="posts", on_delete=models.CASCADE)
#property
def num_comments(self):
return Comment.objects.filter(post_connected=self).count()
def __str__(self):
return f'{self.author or ""} – {self.post_title[:40]}'
def get_absolute_url(self):
return reverse('post-detail', args=[str(self.id)])
def get_absolute_url(self):
return "/blog/{}/".format(self.pk)
class Meta:
ordering = ['-post_created_at']
class Comment(models.Model):
comment_title = models.CharField(max_length=100)
comment = models.TextField(max_length=1000)
comment_created_at = models.DateTimeField(auto_now_add=True)
comment_updated_at = models.DateTimeField(auto_now=True)
commenter = models.ForeignKey(User, related_name="commented", null=True, blank=True, on_delete=models.CASCADE)
active = models.BooleanField(default=False)
post_connected = models.ForeignKey(Post, related_name='comment', on_delete=models.CASCADE, default=None, null=True) #
class Meta:
ordering = ['comment_created_at']
def __str__(self):
return str(self.commenter) + ' : ' + self.comment_title[:40]
def get_absolute_url(self):
return reverse('post-detail', args=[str(self.id)])
### function-based views.py (clumsy, but the form renders and saves)
def post_detail(request, pk):
template_name = 'post_detail.html'
post = get_object_or_404(Post, pk=pk)
comments = post.comment.filter(active=True)
new_comment = None
# Comment posted
if request.method == 'POST':
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.post_connected = post
new_comment.save()
return redirect('/')
else:
comment_form = CommentForm()
return render(request, 'post_detail.html', {'post': post,
'comments': comments,
'new_comment': new_comment,
'comment_form': comment_form})
### function-based template (everything works):
<div class="col-md-8 card mb-4 mt-3 ">
<div class="card-body">
<!-- comments -->
<h2>{{post.num_comments}} comments:</h2>
{% for comment in comments %}
<div class="comments" style="padding: 10px;">
<p class="font-weight-bold">
{{ comment.comment_title }} - {{comment.commenter}}
<span class=" text-muted font-weight-normal">
{{ comment.comment_created_at }}
</span>
</p>
{{ comment.comment | linebreaks }}
</div>
{% endfor %}
</div>
</div>
<div class="col-md-8 card mb-4 mt-3 ">
<div class="card-body">
<h3>Leave a comment</h3>
<form method="post" style="margin-top: 1.3em;">
{{ comment_form.as_p }}
{% csrf_token %}
<button type="submit" class="btn btn-primary btn-lg">Submit</button>
</form>
</div>
</div>
urls.py (for both views, I comment out the url (and view) not in use)
urlpatterns += [
path('blog/<int:pk>/', views.post_detail, name='post_detail'),
#path('blog/<int:pk>/', views.PostDetailView.as_view(), name='post-detail'),
]
Same models.py, but class-based view:
### Class-based view (form doesn't get rendered at all)
class PostDetailView(generic.DetailView):
# pass
model = Post
form_class = CommentForm
def post_detail_view(self, request, primary_key):
post = get_object_or_404(Post, pk=primary_key)
post_connected = Comment.objects.filter(
post_connected=self.get_object()).order_by('-comment_created_at')
comments = post_connected
return comments
def get_success_url(self):
return reverse('post-detail', kwargs={'pk' : self.object.pk})
def get_form_kwargs(self, *args, **kwargs):
kwargs = super(PostDetailView, self).get_form_kwargs(
*args, **kwargs)
return kwargs
if self.request.user.is_authenticated:
comment_form = CommentForm()
comment_form = CommentForm(instance=selfrequest.user)
new_comment = CommentForm(request.POST)
new_comment.save()
def get_object(self):
post = super().get_object()
post_connected = Comment.objects.filter(
post_connected=self.get_object()).order_by('-comment_created_at')
if self.request.user.is_authenticated:
self.request.user = CommentForm(instance=self.request.user)
comment = CommentForm(request.POST)
comment.save()
post.author = User.objects.filter(id=post.author.id)
post.views +=1
post.save()
return post
The code for the templates is identical for both views, but the files are in different directories:
<div class="col-md-8 card mb-4 mt-3 ">
<div class="card-body">
<!-- comments -->
<h2>{{post.num_comments}} comments:</h2>
{% for comment in comments %}
<div class="comments" style="padding: 10px;">
<p class="font-weight-bold">
{{ comment.comment_title }} - {{comment.commenter}}
<span class=" text-muted font-weight-normal">
{{ comment.comment_created_at }}
</span>
</p>
{{ comment.comment | linebreaks }}
</div>
{% endfor %}
</div>
</div>
<div class="col-md-8 card mb-4 mt-3 ">
<div class="card-body">
<h3>Leave a comment</h3>
<form action="{% url 'post-detail' post.id %}" method="POST" style="margin-top: 1.3em;">
{{ comment_form.as_p }}
{% csrf_token %}
<button type="submit" class="btn btn-primary btn-lg">Submit</button>
</form>
</div>
</div>

Django UNIQUE constraint failed: players_comment.user_id

I'm trying to post comment but it's not getting posted, rather this error is appearing UNIQUE constraint failed: players_comment.user_id. I don't know why this error is occuring.
My forms.py:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('body', 'transfernews')
My models.py :
class Transfernews(models.Model):
player_name = models.CharField(max_length=255)
player_image = models.CharField(max_length=2083)
player_description = models.CharField(max_length=3000)
date_posted = models.DateTimeField(default=timezone.now)
class Comment(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
transfernews = models.ForeignKey(Transfernews, related_name="comments", on_delete=models.CASCADE)
body = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '%s - %s' % (self.transfernews.player_name, self.user.username)
My views.py:
def transfer_targets(request):
transfernews = Transfernews.objects.all()
form = CommentForm(request.POST or None)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.user = request.user
new_comment.save()
return redirect('transfernews/')
return render(request, 'transfernews.html', {'transfernews': transfernews, 'form': form})
My transfernews.html:
{% for transfer in transfernews %}
{% if not transfer.comments.all %}
No comments Yet...
{% else %}
{% for comment in transfer.comments.all %}
<strong>
{{ comment.user.username }} - {{ comment.date_added }}
</strong>
<br/>
{{ comment.body }}
<br/><br/>
{% endfor %}
{% endif %}
<hr>
<div>Comment and let us know your thoughts</div>
<form method="POST">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary btn-sm shadow-none" type="submit">Post comment</button>
<button class="btn btn-outline-primary btn-sm ml-1 shadow-none" type="button">Cancel</button>
</form>
{% endfor %}
In models.py, in the comment class, change
user = models.OneToOneField(User, on_delete=models.CASCADE)
to
user = models.ForeignKey(to=User, on_delete=models.CASCADE)
One to one works both ways, user's allowed to only have one comment and a comment can belong to only one user. By changing to one to many via foreignkey you'll preserve the latter and get rid of the former constraint.

(DJANGO) How to set current.username in author comment section

I have Comment model in my Django app.
I want to make comment author to be current registered author, not another registered account.
Here is screenshot, I can choose user2 account to post the comment, but currently I'm on user1 account.
Here is my Django Comment model:
class Comment(models.Model):
post = models.ForeignKey(Post,on_delete=models.CASCADE,related_name='comments')
name = models.ForeignKey(User, on_delete=models.CASCADE)
email = models.EmailField()
body = models.TextField()
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.name)
And some code from html form:
<article class="media content-section">
<div class="media-body">
<!-- comments -->
{% if comments.count == 1 %}
<h2>{{ comments.count }} comment</h2>
{% else %}
<h2>{{ comments.count }} comments</h2>
{% endif %}
{% for comment in comments %}
<div class="comments" style="padding: 10px;">
<p class="font-weight-bold">
{{ comment.name }}
<span class=" text-muted font-weight-normal">
{{ comment.created_on }}
</span>
</p>
{{ comment.body | linebreaks }}
</div>
{% endfor %}
{% if new_comment %}
<div class="alert alert-success" role="alert">
Your comment is awaiting moderation
</div>
{% else %}
<h3>Leave a comment</h3>
{% load crispy_forms_tags %}
<form method="post" style="margin-top: 1.3em;">
{{ comment_form | crispy }}
{% csrf_token %}
<button type="submit" class="btn btn-primary btn-lg">Submit</button>
</form>
{% endif %}
</div>
</article>
EDIT
view.py
def post_detail(request, slug):
template_name = 'blog/post_detail.html'
post = get_object_or_404(Post, slug=slug)
comments = post.comments.filter(active=True)
new_comment = None
# Comment posted
if request.method == 'POST':
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
# Create Comment object but don't save to database yet
new_comment = comment_form.save(commit=False)
# Assign the current post to the comment
new_comment.post = post
# Save the comment to the database
new_comment.save()
else:
comment_form = CommentForm()
return render(request, template_name, {'posts': post,
'comments': comments,
'new_comment': new_comment,
'comment_form': comment_form})
You can make the field non-editable, by setting the editable=… parameter [Django-doc] to False. I furthermore advise to use user and not name, since it is a reference to the user object, not the name of the user:
class Comment(models.Model):
post = models.ForeignKey(Post,on_delete=models.CASCADE,related_name='comments')
user = models.ForeignKey(User, editable=False, on_delete=models.CASCADE)
email = models.EmailField()
body = models.TextField()
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.user)
Then in your view, when the form is valid, you set the instance.user of that form to the request.user:
from django.contrib.auth.decorators import login_required
#login_required
def add_comment(request, post_id):
# …
if request.method == 'POST':
form = CommentForm(request.POST, request.FILES)
if form.is_valid():
form.instance.user = request.user
# …
# …
# …
Well, there shouldn't be a choice of users. To identify user just use request.user in your view. I don't know how your view looks like, but you can set name like this:
name = request.user
# or if you're using classes:
name = self.request.user

Only allowing the logged in user to submit a comment on a post

I am having trouble getting the author of the comment when the form submits
This works fine for my post method, not so much for commenting on the post
Models.py
class Post(models.Model):
title = models.CharField(max_length=100)#title of a post
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE) #if a user is deleted all of their post will be as well
view_count = models.IntegerField(default=0)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
class Comment(models.Model):
post = models.ForeignKey('forum.Post', on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE)
text = models.TextField(null=True)
created_date = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.text
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
Views.py
class CommentCreate(LoginRequiredMixin,CreateView):
model = Comment
fields = ['text']
def for_valid(self,form):
form.instance.author = self.request.users
return super().form_valid(form)
class PostCreate(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
def form_valid(self,form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostDetail(DetailView):
model = Post
def get_context_data(self, **kwargs):
data = super(PostDetail, self).get_context_data(**kwargs)
vc = self.object.view_count
self.object.view_count = F('view_count') + 1
self.object.save()
self.object.view_count = vc + 1
return data
Post_detail template
{% extends "forum/base.html" %}
{% block content %}
<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="#">{{ object.author }}</a>
<small class="text-muted">{{ object.date_posted }}</small>
<small class="text-muted">{{ object.view_count }}</small>
{% if object.author == user %}
<div>
<a class="btn btn-secondary btn-sm mt-1 mb-1" href="{% url 'post-update' object.id %}">Update</a>
<a class="btn btn-danger btn-sm mt-1 mb-1" href="{% url 'post-delete' object.id %}">Delete</a>
</div>
{% endif %}
</div>
<h2 class="article-title">{{ object.title }}</h2>
<p class="article-content">{{ object.content }}</p>
<a class="btn btn-default" href="{% url 'add_comment_to_post' pk=post.pk %}">Add comment</a>
{% for comment in post.comments.all %}
<div class="comment">
<div class="date">{{ comment.created_date }}</div>
<strong>{{ comment.author }}</strong>
<p>{{ comment.text|linebreaks }}</p>
</div>
{% empty %}
<p>No comments here yet :(</p>
{% endfor %}
</div>
</article>
{% endblock content %}
The error I am getting is "NOT NULL constraint failed: forum_comment.author_id". Im not sure what is different from my post CreateView
Create a ModelForm for Comment in a forms.py file inside your app:
# your_app/forms.py
from django import forms
from .models import Comment
class CommentModelForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['post', 'text']
widgets = {
'post': forms.HiddenInput(),
'text': forms.Textarea(attrs={'placeholder': 'Write your comment here...'}),
}
After that edit your PostDetail view so you can pass the CommentModelForm with the post instance already set in the form:
class PostDetail(DetailView):
model = Post
def get_context_data(self, **kwargs):
data = super(PostDetail, self).get_context_data(**kwargs)
vc = self.object.view_count
self.object.view_count = F('view_count') + 1
self.object.save()
self.object.view_count = vc + 1
initial_comment_data = {
'post': self.object,
}
data['comment_form'] = CommentModelForm(initial=initial_comment_data)
return data
Add the CommentModelForm to you CommentCreate view:
class CommentCreate(LoginRequiredMixin,CreateView):
model = Comment
form_class = CommentModelForm
def form_valid(self,form):
form.instance.author = self.request.user
return super().form_valid(form)
And finally, the template of your PostDetail should look like this:
# your_app/templates/your_app/post_detail.html
...
<form method="POST" action="{% url 'yourapp:comment-create' %}">
{% csrf_token %}
{{ comment_form }}
<button type="submit">Place a comment</button>
</form>
<div>
{% for comment in post.comments.all %}
<p>{{ comment.text }} - by {{ comment.author }} at {{ comment.created_date }}</p>
{% empty %}
<p>No comments yet. Be the first one!</p>
{% endfor %}
</div>
You have two typos
for_valid should be form_valid
def form_valid(self,form):
form.instance.author = self.request.users
return super().form_valid(form)
and self.request.users should be self.request.user
Try self.request.user instead of self.request.users. And also change for_valid to form_valid. If that doesn't work try something like this. new_comment is the instance that you can save here and return from the function instead of the form.
Inside the form_valid function of CommentCreate class
new_post = form.save(commit=False)
new_post.author = self.request.user
new_post.save()
return new_post
Inside the form_valid function of PostCreate class :
new_comment = form.save(commit=False)
new_comment.author = self.request.user
new_comment.post = super().form_valid(form)
new_comment.save()
return new_comment

django - pass multiple instance into form and save it in DB

I have a view where they are multiple posts and I want when the user like one of them, the form take the user_id and the post_id and save it into the DB. This is th Models.py:
class LikePost(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Posts, on_delete=models.CASCADE)
def __str__(self):
return '{} - {}'.format(self.user.username, self.post.name)
Forms.py:
class LikePostForm(forms.ModelForm):
class Meta:
model = LikedShops
fields = ['user', 'post']
widgets = {
'user': forms.HiddenInput(),
'post': forms.HiddenInput()
}
Views.py:
def posts(request):
if request.method == 'POST':
form = LikePostForm(request.POST)
if form.is_valid():
u = form.save(commit=False)
u.user = request.user
u.save()
return redirect('posts')
else:
form = LikePostForm()
context = {
'posts': Posts.objects.all(),
'form': form
}
return render(request, "posts.html", context)
and this the form in posts.html:
{% for post in posts %}
<div class="col-md-3">
<article class="card mb-4">
<header class="card-header">
<h4 class="card-title"><b>{{ post.name }}</b></h4>
</header>
<img style="width: 100%; height: 150px;" class="card-img" src="{{ post.image.url }}"/>
<div class="card-body">
<p class="card-text">{{ post.description }}</p>
</div>
{% if user.is_authenticated %}
<div class="card-footer">
<div class="row">
<div class="col">
<form action="/posts/" method="post">
{% csrf_token %}
{{ l_form|crispy }}
<button type="submit" class="btn btn-outline-success">Like</button>
</form>
</div>
</div>
</div>
{% endif %}
</article><!-- /.card -->
</div>
{% endfor %}
This is my edit, I did what you said, I made changes to:
forms.py:
class Meta:
model = Liked
fields = ['user', 'post']
widgets = {
'user': forms.HiddenInput(),
'post': forms.HiddenInput()
}
posts.html:
<form action="/posts/" method="post">
{% csrf_token %}
<input type="hidden" name="post" value="{{ post.pk }}">
{{ l_form|crispy }}
<button type="submit" class="btn btn-outline-success">Like</button>
</form>
views.py:
def posts(request):
if request.method == 'POST':
l_form = LikePostForm(request.POST, instance=request.user.profile)
if l_form.is_valid():
u = l_form.save(commit=False)
u.post = Posts.objects.filter(pk=l_form.cleaned_data.get('post')).first()
u.save()
messages.success(request, f"Form is valid!")
else:
messages.warning(request, f'Form is not valid! {request.POST}')
else:
l_form = LikePostForm(instance=request.user.profile)
context = {
'post': Posts.objects.all(),
'l_form': l_form
}
return render(request, "posts.html", context)
Now when I click the Like button, I got this message **Form is not valid! <QueryDict: {'csrfmiddlewaretoken': ['cNk9ZDS33Nj0l95TBfwtedL1jjAbzDSrH15VjMNZAcxjQuihWNZzOkVnIyRzsjwN'], 'post': ['1', ''], 'user': ['1']}>**
There are a couple of issues with your code.
First, the __str__() method should return a string and not a tuple
class LikePost(models.Model):
...
def __str__(self):
return '{} - {}'.format(self.user.username, self.post.name)
Second, there is a typo; change Pots to Posts:
context = {
'posts': Posts.objects.all(),
'form': form,
}
return render(request, "posts.html", context)
And third and last, the line u.post = request.post is throwing the error you mention, because the request object has no attribute post.
So change your form code to add the post in hidden state (I used fields instead of exclude):
class LikePostForm(forms.ModelForm):
class Meta:
model = LikePost
fields = ['post', ]
widgets = {
'post': forms.HiddenInput(),
}
and then change your view:
form = LikePostForm(request.POST)
if form.is_valid():
u = form.save(commit=False)
u.user = request.user
u.save()
After edit to the question:
Try adding post.pk as a hidden input in your form:
<form action="/posts/" method="post">
{% csrf_token %}
<input type="hidden" name="post" value="{{ post.pk }}">
{{ l_form|crispy }}
<button type="submit" class="btn btn-outline-success">Like</button>
</form>
or you can also do in your view:
u.post = Posts.objects.filter(pk=form.cleaned_data.get('post')).first()