'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>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
