30👍
Use FormMixin
if you want combine DetailView
and a form:
from django.shortcuts import render, get_object_or_404, redirect
from django.views.generic.detail import DetailView
from django.views.generic.edit import FormMixin
from django.urls import reverse
from .models import Post, Comment
from .forms import CommentForm
class ParticularPost(FormMixin, DetailView):
template_name='blog/post.html'
model = Post
form_class = CommentForm
def get_success_url(self):
return reverse('post_detail', kwargs={'pk': self.object.id})
def get_context_data(self, **kwargs):
context = super(ParticularPost, 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():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
form.save()
return super(ParticularPost, self).form_valid(form)
And don’t forget to add the post
field into the form (you can do it hidden):
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('author', 'text', 'post',)
And the better way to add a creation date – use auto_now_add=True
:
created_date = models.DateTimeField(auto_now_add=True)
2👍
As several people have mentioned in the comments for Anton Shurashov‘s answer, while the solution provided works it is not the solution that the devs recommend in the Django docs.
I followed the alternate solution given in the docs for a project that seems quite similar to OP’s. Hopefully this solution will be useful for anyone else trying to solve this same problem.
First I created DetailView and defined my own get_context_data method to add the form to the context:
from django.shortcuts import render
from django.views import View
from django.views.generic import ListView, DetailView
from django.views.generic.edit import FormView
from django.views.generic.detail import SingleObjectMixin
from django.http import Http404, HttpResponseForbidden
from .models import BlogPost, Comment
from users.models import BlogUser
from .forms import CommentForm
class BlogPostDetailView(DetailView):
"""
Shows each individual blog post
and relevant information.
"""
model = BlogPost
template_name = 'blog/blogpost_detail.html'
context_object_name = 'blog_post'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = CommentForm()
return context
Then in my FormView view, I defined the post method to be run when a user submits the form (adds a new comment).
(One note, I set the success_url = ‘#’ so that the form would stay on the same page. There are a myriad of ways to accomplish this, but this was the easiest one for me.):
class CommentFormView(SingleObjectMixin, FormView):
"""
View for the comment form, which allows users to
leave comments user a blog post if logged in.
"""
template_name = 'blog/blogpost_detail.html'
form_class = CommentForm
model = Comment
success_url = '#'
def post(self, request, *args, **kwargs):
"""
Posts the comment only if the user is logged in.
"""
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
return super().post(request, *args, **kwargs)
The final View brings everything together, and is a simple View, where the get method calls the BlogPostDetailView (Detail View) and the post method calls the CommentFormView.
Within the post method I also create a form object to automatically set the current user to the author of the comment and the blog post to the current blog post that the page is showing.
class PostView(View):
def get(self, request, *args, **kwargs):
view = BlogPostDetailView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = CommentFormView.as_view()
form = CommentForm(request.POST)
# Set the current user
# to the comment_author field
form.instance.comment_author = request.user
# Set the blog post as the current blogpost
form.instance.post = BlogPost.objects.get(id=self.kwargs['pk'])
if form.is_valid():
form.save()
return view(request, *args, **kwargs)
In my forms.py I have defined my CommentForm Model like so (I set the label to an empty string so that the label ‘content’ did not show up above the new comment):
from django import forms
from ckeditor.widgets import CKEditorWidget
from .models import Comment
class CommentForm(forms.ModelForm):
"""
Gives the option to add a comment to the bottom of a Blog Post,
but only for logged in users.
"""
content = forms.CharField(widget=CKEditorWidget(), label='')
class Meta:
model = Comment
fields = [ 'content',]
class Media:
css = {
'all': ('forms.css',)
}
- [Django]-Access web server on VirtualBox/Vagrant machine from host browser?
- [Django]-Naming convention for Django URL, templates, models and views
- [Django]-How to test auto_now_add in django
0👍
It’s not necessary to populate the form with initial. I will extend the above solution.
def form_valid(self, form):
post = self.get_object()
myform = form.save(commit=False)
myform.post = post
form.save()
return super(ParticularPost, self).form_valid(form)
- [Django]-No handlers could be found for logger
- [Django]-Django Rest Framework pagination extremely slow count
- [Django]-Extend base.html problem