Django pass PK from post to next page - django

I currently have a blog post feature on my site. I want users to be able to create sub posts
(build log ) off of the main posts.
I created this model with a FK to the Post model
class BuildLog(models.Model):
title = models.CharField(max_length=100)
content = RichTextUploadingField(blank=True, null=True)
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey('Post', on_delete=models.CASCADE)
I then added a button on the post page and created a form. Everything is working as expected but when I submit the form I get the error
null value in column "post_id" of relation "blog_buildlog" violates not-null constraint
If I understand the error correctly it is saying I am not passing the PK of the post into the form.
Views
def DetailPostView(request, pk):
model = Post
post = Post.objects.get(pk=pk)
form = CommentForm
comments = Comment.objects.filter(post=post)#.order_by('-create')
if request.method == 'POST':
# A comment was posted
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.author = request.user
new_comment.post = post
new_comment.save()
context = {
'post':post, 'form':form, 'comments':comments
}
return render(request, 'blog/post_detail.html', context)
class BuildLogView(LoginRequiredMixin, CreateView):
model = BuildLog
form_class = BuildLogForm
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def save(self, *args, **kwargs):
super(BuildLog,self).save(*args, **kwargs)
Url
path('build-log-form/', BuildLogView.as_view(), name='build-log-form'),
path('post/<int:pk>/', views.DetailPostView, name='post-detail')
How do I go about passing the post PK into the next view?
forms.py
class BuildLogForm(ModelForm):
class Meta:
model = BuildLog
fields = ['title' , 'content']
html
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="post" enctype="multipart/form-data" id="PostForm" novalidate>
<!--id="modelForm"-->
{% csrf_token %}
<fieldset class="django-ckeditor-widget">
<legend class="border-bottom mb-4">New Build Log</legend>
{{ form.title | as_crispy_field }}
{{ form.media }}
{{ form.content | as_crispy_field | safe}}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Post</button>
</div>
</form>
</div>
{% endblock content %}
Just incase their is any confusing It goes Post --> BuildLog(subpost)

Related

Django how to add a form to a DetailView with FormMixin

I am attempting to add a form for comments to a DetailView. The DetailView displays notes for specific projects. So the comments have a foreign key that is the specific note and the note has a foreign key for the specific project.
I am attempting to use FormMixin with DetailView. So far I have not bee successful. Currently I can get the form to display but it does not save and in the terminal I see the following error Method Not Allowed (POST): /projects/project/1/note/1/
I can get these to work separately but not with the form in the DetailView.
Here are my models:
class ProjectNotes(models.Model):
title = models.CharField(max_length=200)
body = tinymce_models.HTMLField()
date = models.DateField(auto_now_add=True)
project = models.ForeignKey(Project, default=0, blank=True, on_delete=models.CASCADE, related_name='notes')
def __str__(self):
return self.title
class ProjectNoteComments(models.Model):
body = tinymce_models.HTMLField()
date = models.DateField(auto_now_add=True)
projectnote = models.ForeignKey(ProjectNotes, default=0, blank=True, on_delete=models.CASCADE, related_name='comments')
The View:
class ProjectNotesDetailView(DetailView, FormMixin):
model = ProjectNotes
id = ProjectNotes.objects.only('id')
template_name = 'company_accounts/project_note_detail.html'
comments = ProjectNotes.comments
form_class = NoteCommentForm
def form_valid(self, form):
projectnote = get_object_or_404(ProjectNotes, id=self.kwargs.get('pk'))
comment = form.save(commit=False)
comment.projectnote = projectnote
comment.save()
return super().form_valid(form)
def get_success_url(self):
return reverse('project_detail', args=[self.kwargs.get('pk')])
The form:
class NoteCommentForm(forms.ModelForm):
class Meta:
model = ProjectNoteComments
fields =['body',]
widgets = {
'body': forms.TextInput(attrs={'class': 'form-control'})
}
The template:
% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="section-container container">
<div class="project-entry">
<h2>{{ projectnotes.title }}</h2>
<p>{{ projectnotes.body | safe }}</p>
</div>
<div><b>Comments on {{projectnotes.title}}</b></div>
{% if projectnotes.comments.all %}
{% for comment in projectnotes.comments.all %}
<div class="notecomments" style="padding: 10px;">
{{ comment.body | safe }}
</div>
{% endfor %}
{% else %}
<p>No comments have been have been added yet.</p>
{% endif %}
<h2>add note</h2>
<h1>Add Comment</h1>
<form action="" method="post">
{% csrf_token %}
{{ form.media }}
{{ form|crispy }}
<input type="submit" value="save">
</form>
{% endblock content %}
Try to change the order between DetailView and FormMixin in ProjectNotesDetailView then implement the post method (enabled by the `FormMixin):
class ProjectNotesDetailView(FormMixin, DetailView):
model = ProjectNotes
id = ProjectNotes.objects.only('id')
template_name = 'company_accounts/project_note_detail.html'
comments = ProjectNotes.comments
form_class = NoteCommentForm
def form_valid(self, form):
projectnote = get_object_or_404(ProjectNotes, id=self.kwargs.get('pk'))
comment = form.save(commit=False)
comment.projectnote = projectnote
comment.save()
return super().form_valid(form)
def get_success_url(self):
return reverse('project_detail', args=[self.kwargs.get('pk')])
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Check how to use formmixin with detailview (documentation).

NOT NULL constraint failed: social_media_app_blogcomment.user_id

I'm making this comment system for my blogs.. I already made the model, the ModelForm and the view to display the comments and the blog. I'm just really confused how to save the comments related to a specific blog. I tried to save the comments with a view but I face an IntegrityError. A little help would be appreciated.
Here's my views.py:
#login_required #View to show the blogs and comments related to it
def readblog(request, blog_pk):
Blog = get_object_or_404(blog, pk=blog_pk)
return render(request, 'social_media/readblog.html', {'Blog':Blog,'Form':CommentForm()})
#login_required #view to save the comments
def commentblog(request,blog_pk):
Blog = get_object_or_404(blog,pk=blog_pk)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
Form = form.save(commit=False)
Form.Blog = Blog
Form.save()
return redirect('usersfeed')
Urls.py:
path('commentblog/<int:blog_pk>', views.commentblog, name='commentblog'),
path('readblog/<int:blog_pk>', views.readblog, name='readblog'),
HTML Page to write and save comments (along with the blog):
{{ Blog.title }}
<br>
{{ Blog.text }}
<br>
{% if Blog.image %}
<img src="{{ Blog.image.url }}" alt="">
{% endif %}
<br>
<form action="{% url 'commentblog' Blog.id %}" method="post">
{% csrf_token %}
{{ Form.as_p }}
<button type="submit">Comment!</button>
</form>
{% for i in Blog.BlogComment.all %}
{{ i.comment }}
<b>user:{{ i.user }}</b>
<br>
{% endfor %}
Comment's model:
class BlogComment(models.Model): # --run-syncdb <- (Research about this!)
user = models.ForeignKey(User, on_delete=models.CASCADE)
comment = models.CharField(max_length=250, null=True)
blog = models.ForeignKey(blog, related_name='BlogComment', on_delete=models.CASCADE, blank=True, null=True)
def __str__(self):
return self.comment
Forms.py:
class CommentForm(forms.ModelForm):
class Meta:
model = BlogComment
fields = ['comment']
You need to add the user since it's a not null field in your model:
def commentblog(request,blog_pk):
blog_obj = get_object_or_404(blog,pk=blog_pk)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
form_obj = form.save(commit=False)
form_obj.blog = blog_obj
# add user instance
form_obj.user = request.user
form_obj.save()
return redirect('usersfeed')

(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

Associating a model with another model when saving it

I have the following code. The product is like this: There's a book list. On each book item is a form. This form saves a post and makes it related to this particular book. When you click a book item, you see a list of posts, all related to this book item. So far the book list works fine, saving post data works fine, and when you click a list, it (the DetailView) shows the list of posts (instead of book description, which is usually how DetailView is used) fine. What's not working is saving the post to be related to the particular book on whom is the form that saves the post. More simply, when you save a post, the post should be related to the book that the form is located in.
I'd really appreciate your help. Thanks!
views.py:
class BookList(ListView):
model = Book
template_name='books/books.html'
class PostForm(ModelForm):
class Meta:
model = Post
# Previously I tried, unsuccessfully, this among others:
# books = Book.objects.filter(book)
# posts = Post.objects.filter(the_book=books)
# model = posts
fields = ['post']
widgets = {
'post': forms.Textarea()
}
def get_context_data(self, **kwargs):
context = super(BookList, self).get_context_data(**kwargs)
context['form'] = BookList.PostForm
return context
def post(self, request, *args, **kwargs):
form = BookList.PostForm(request.POST)
if form.is_valid():
form.save()
return render(request, self.template_name, {'form': form })
class Posts(DetailView):
model = Book
template_name='books/post_create.html'
slug_field = 'id'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
book = self.object
posts = Post.objects.filter(the_book=book)
context['posts'] = posts
return context
models.py:
class Book(models.Model):
book = models.CharField(max_length=1000, blank=False, null=False, default="1")
def __str__(self):
return self.book
class Post(models.Model):
post = models.CharField(max_length=1000, blank=False, null=False)
the_book = models.ForeignKey('Book', on_delete=models.CASCADE, default="1")
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.post
books.html:
<ul>
{% for book in object_list %}
<div class='ui card'>
<a class="content" href='{% url 'books:posts' book.id %}'>
<div class="header">{{ book }}</div>
</a>
<div class="ui bottom attached button">
<form class='ui form' action='' method='post'> {% csrf_token %}
{{ form.as_p }}
<div class='postbutton'>
<input class='ui button' type="submit" value="Done" />
</div>
</form>
</div>
</div>
{% empty %}
<h5>You don't have any books!</h5>
{% endfor %}
post_create.html (the name will be changed later...):
<ul>
{% for post in posts %}
<div class='ui card'>
<a class="content">
<div class="header">{{ post }}</div>
</a>
</div>
{% empty %}
<h5>You don't have any posts!</h5>
{% endfor %}
Try this: add a book_id hidden input in your form:
<form class='ui form' action='' method='post'> {% csrf_token %}
{{ form.as_p }}
<input type="hidden" name="book_id" value="{{ book.id }}">
<div class='postbutton'>
<input class='ui button' type="submit" value="Done" />
</div>
and use it in your BookList view:
def post(self, request, *args, **kwargs):
form = BookList.PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.the_book_id = request.POST['book_id']
post.save()
return render(request, self.template_name, {'form': form })

Django - Comment form

I have 4 models: User, Blogger, Post and Comment.
Now, in 'post_desc.html', i want to insert a comment box.
{% if user.is_authenticated %}
<form method="post">
{% csrf_token %}
<input type="text" name="comment" style="width: 800px; height: 145px;"></br></br>
<button type="submit">Submit Comment</button>
</form>
{% else %}
<p>Login to comment</p>
{% endif %}
So, this form will only take comment from the user.
But how to store information like 'commented_by' which will the user that is currently logged in and 'commented_on' which will be the post_topic in which he/she is commenting.
How to store these information automatically? in views.py i tried 'request.user' but that didn't worked. Any solutions?
Comment model:
class Comment(models.Model):
commented_by = models.ForeignKey(User, related_name='comments')
commented_on = models.ForeignKey(Post, related_name='comments')
commented_text = models.CharField(max_length=500)
commented_time = models.DateTimeField(auto_now_add=True)
Post model:
class Post(models.Model):
topic = models.CharField(max_length=200)
description = models.TextField()
created_by = models.ForeignKey(User, related_name='posts')
created_on = models.DateTimeField()
I did this in my view.py
def post_desc(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
comment = request.POST['comment']
comments = Comment.objects.create(
commented_text = comment,
commented_on = request.topic,
commented_by = request.user
)
return redirect('post_desc', pk=post.pk)
return render(request, 'post_desc.html', {'post': post})
But it is giving error, "'WSGIRequest' object has no attribute 'post'".
The only thing wrong with your code is that there's no such thing as request.topic. The topic is the Post, which you already have.
comments = Comment.objects.create(
commented_text = comment,
commented_on = post,
commented_by = request.user
)
you have to a create a url that have a pk parameter to know where to post the comment in which post?:
url(r'add/comment/post/(?P<pk>[0-9]+)', views.new_comment, name="comment")
now let's create the view new_comment:
from django.utils import timezone
def new_comment(request, pk):
user = request.user
comment_text = request.POST['your-comment-input-name']
post = Post.objects.get(id=pk)
Comment.objects.create(created_by=user, text=comment_text, post=post, create_at=timezone.localtime(timezone.now()))
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
don't forget to set your form action to action="{% url 'comment' Post.id %}"
I have something like that in my blog project which I develop for improve myself in django python.
my model;
class Comment(models.Model):
post = models.ForeignKey('post.Post', related_name='comments', on_delete=models.CASCADE)
name = models.CharField(max_length=150, verbose_name='Your Name')
comment = models.TextField(verbose_name='Your Comment')
created_date = models.DateTimeField(auto_now_add=True)
my view for post detail page;
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
form = CommentForm(request.POST or None)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return HttpResponseRedirect('your redirect page')
context = {
'post': post,
'form': form,
}
return render(request, 'post/detail.html', context)
my post detail template page;
<h3>Comments;</h3>
{% if post.comments.count <= 0 %}
<h4>No comment yet!</h4>
{% else %}
{% for comment in post.comments.all %}
<h4>{{ comment.name }} |
<small>{{ comment.created_date|timesince }}</small>
</h4>
<p>{{ comment.comment|linebreaks }}</p>
<hr/>
{% endfor %}
{% endif %}
<h3>Add Comment:</h3>
{% include 'post/comment.html' %}
my comment form template;
{% load crispy_forms_tags %}
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<input class="btn btn-primary" type="submit" value="Add Comment"/>
</form>
Hope this helps.