I created an app where user can login and post a contents he want, and now I decided to add comment section to each post user did in the app, I followed the tutorial in djangocentral website, but it's not working after I added everything into my app, when I click on (Read more), it's throw me an error in the browser: local variable 'comment_form' referenced before assignment if I deleted the comment_form inside the context of my viewPhoto view its shows nothing in viewPhoto template.
the model:
class Comment(models.Model):
post = models.ForeignKey(Photo, on_delete=models.CASCADE, related_name='comments')
name = models.CharField(max_length=80)
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)
the admin.py:
from django.contrib import admin
from .models import Photo, Category, Comment
# Register your models here.
admin.site.register(Category)
admin.site.register(Photo)
#admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ('name', 'body', 'post', 'created_on', 'active')
list_filter = ('active', 'created_on')
search_fields = ('name', 'email', 'body')
actions = ['approve_cooments']
def approve_comment(self, request, queryset):
queryset.update(active=True)
the form.py:
from dataclasses import fields
from pyexpat import model
from .models import Comment
from django import forms
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('name', 'email', 'body',)
the view.py:
def viewPhoto(request, pk):
post = get_object_or_404(Photo, id=pk)
photo = Photo.objects.get(id=pk)
template_name = 'photo.html'
comment = post.comments.filter(active=True)
new_comment = None
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 = post
new_comment.save()
else:
comment_form = CommentForm()
return render(request, 'photo.html', {'photo': photo, 'post': post,
'comment':comment,
'new_comment': new_comment,
'comment_form': comment_form})
viewPhoto template:
<body class="m-5">
<div class="container">
<div class="row justify-content-center">
<div class="col">
<div style="height: 90vh;">
<img style="max-width: 100%; max-height: 100%" src="{{ post.image.url }}" alt="">
<p>{{post.description}}</p>
<p>{{post.user.username.upper}}</p>
<p>{{post.date_added}}</p>
</div>
</div>
</div>
</div>
<div class="container">
{% for comment in comments %}
<p>{{ comment.name }}</p>
<br>
<p>{{ comment.created_on }}</p>
<br>
<p>{{ comment.body }}</p>
{% endfor %}
</div>
<div class="container">
{% if new_comments %}
<p>wait your comments is ready</p>
<form method="POST">
{% csrf_token %}
{{ comment_form.as_p }}
<button type="submit">submit</button>
</form>
{% endif %}
</div>
The else should bind with the if request.method == 'POST' check, not with the if comment_form.is_valid() check, so:
from django.shortcuts import redirect
def viewPhoto(request, pk):
post = get_object_or_404(Photo, id=pk)
photo = Photo.objects.get(id=pk)
template_name = 'photo.html'
comment = post.comments.filter(active=True)
new_comment = None
if request.method == 'POST':
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
comment_form.instance.post = post
comment_form.save()
return redirect('name-of-some-view')
else:
comment_form = CommentForm()
return render(request, 'photo.html', {'photo': photo, 'post': post,
'comment':comment,
'new_comment': new_comment,
'comment_form': comment_form})
Note: In case of a successful POST request, you should make a redirect
[Django-doc]
to implement the Post/Redirect/Get pattern [wiki].
This avoids that you make the same POST request when the user refreshes the
browser.
Related
Django, how to display comments on a blog post in post template only for login user , i made these below mentioned models & views. Now my comments are successfully storing in database with name of user but the problem is its not shown on post . so far only count is displaying to see how many comments posted so far!
def get_comments(self):
return self.comments.all().order_by('-timestamp')
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
commentfield = models.TextField()
post = models.ForeignKey(Blogpost, related_name='comments', on_delete=models.CASCADE)
def __str__(self):
return self.user.username
**My Forms**
from django import forms
from .models import Blogpost, Comment
class CommentForm(forms.ModelForm):
commentfield = forms.CharField(widget=forms.Textarea(attrs={
'class': 'form-control',
'placeholder': 'Type your comment',
'id': 'usercomment',
'rows': '4'
}))
class Meta:
model = Comment
fields = ("commentfield",)
**My Blog Post Views**
def blogpost(request, year, month, day, post):
category_count= get_category_count()
latest = Blogpost.objects.order_by('-publish')[0:4]
post = get_object_or_404(Blogpost, slug= post)
form = CommentForm(request.POST or None)
if request.method == "POST":
if form.is_valid():
form.instance.user = request.user
form.instance.post = post
form.save()
return redirect('.')
context = {'post':post,
'latest': latest,
'category_count': category_count,
'form': form,
}
return render(request, 'blog/blogpost.html', context)
**My Templates**
<div class="post-comments">
<header>
<h3 class="h6">Post Comments<span class="no-of-comments">
({{ post.comments.count }})</span></h3>
</header>
{% for comment in post.get_comments %}
<div class="comment">
<div class="comment-header d-flex justify-content-between">
<div class="user d-flex align-items-center">
<div class="image">
{% if comment.user.author %}
<img src="{{ comment.user.author.profile_picture.url }}" alt="..."
class="img-fluid rounded-circle">
{% else %}
<img src="" alt="..."
class="img-fluid rounded-circle">
{% endif %}
</div>
<div class="title"><strong>{{ comment.user.username }}</strong><span
class="date">{{ comment.timestamp|timesince }} ago</span></div>
</div>
</div>```
In your blogpost view try getting comments manually and pass them in the context, instead of calling a class method inside the template. Template purpose is to display the buisness logic, not write or define it.
def blogpost(request, year, month, day, post):
category_count= get_category_count()
latest = Blogpost.objects.order_by('-publish')[0:4]
post = get_object_or_404(Blogpost, slug=post)
comments = post.comments.all() # here
form = CommentForm(request.POST or None)
if request.method == "POST":
if form.is_valid():
form.instance.user = request.user
form.instance.post = post
form.save()
return redirect('.')
context = {'post':post,
'latest': latest,
'category_count': category_count,
'form': form,
'comments': comments
}
return render(request, 'blog/blogpost.html', context)
then in your template just use:
{% for comment in comments %}
{{ comment }}
{% endfor %}
i've added to my Blog post comments now the problem is that i'm trying to create remove function for that and i have one but now eve uset thak is logged in can remove all the comments how can i fix this that user can delete only their own comments??
the funnyiest thing is that i have this function on my posts and it wokres and if i'm trying to do the same on my comments then i've get 404 error. Ive tried a few different ways but nothing worked You are my only hope:)
views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.models import User
from .models import Post, Comment
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from .forms import CommentForm
from django.contrib.auth.decorators import login_required
# Create your views here.
def home(request):
context = {
'posts': Post.objects.all()
}
return render(request, 'blog/home.html', context)
class PostListView(ListView):
model = Post
template_name = 'blog/home.html'
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 5
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html'
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-date_posted')
class PostDetailView(DetailView):
model = Post
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
success_url = '/'
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
def about(request):
return render(request, 'blog/about.html', {'title': 'About'})
#login_required
def add_comment_to_post(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.author = request.user
comment.post = post
comment.save()
return redirect('post-detail', pk=post.pk)
else:
form = CommentForm()
return render(request, 'blog/add_comment_to_post.html', {'form': form})
#login_required
def comment_remove(request, pk):
comment = get_object_or_404(Comment, pk=pk)
comment.delete()
return redirect('post-detail', pk=comment.post.pk)
Models.py
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse
# Create your models here.
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
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(Post, on_delete=models.CASCADE, related_name='comments')
author = models.CharField(max_length=30)
text = models.TextField()
created_on = models.DateTimeField(default=timezone.now)
active = models.BooleanField(default=False)
class Meta:
ordering = ['-created_on']
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
post_detail
{% extends "blog/base.html" %}
{% block content %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{post.author.profile.image.url}}">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="{% url 'user_posts' object.author.username %}">{{ object.author }}</a>
<small class="text-muted">{{ object.date_posted|date:"d F, Y" }}</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>
</div>
</article>
<hr>
<a class="btn btn-default" href="{% url 'add_comment_to_post' pk=post.pk %}">Add comment</a>
{% for comment in post.comments.all %}
{% if user.is_authenticated or comment.approved_comment %}
<div class="comment">
<div class="date">
<a class="btn btn-default" href="{% url 'comment_remove' pk=comment.pk %}">remove</a>
</div>
<strong>{{ comment.author }}</strong> {{ comment.created_on|date:"d F, Y G:i" }}
<p>{{ comment.text|linebreaks }}</p>
</div>
{% endif %}
{% empty %}
<p>No comments here yet :(</p>
{% endfor %}
{% endblock content %}
Firstly make author in Comment model Foreign Key(User). Then in views check this condition:
if comment.author == request.user:
comment.delete()
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
I am trying to build a social network in django. In this code I am trying to enter comments to a post through the template box in my template. But the comment is not getting fetched in my database. My code is as below:
My forms.py creates a model form for comments
forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('ctext',)
Models has a seperate comment model which has foreign keys from post model and user model.
models.py
class Post(models.Model):
author = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
imgfile = models.ImageField(upload_to='posts/', blank=True, null=True)
def publish(self):
self.published_date=timezone.now()
self.save()
def __str__(self):
return self.title
class Comment(models.Model):
comment_auth = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
title = models.ForeignKey(Post, on_delete=models.CASCADE)
ctext = models.TextField(blank=True, null=True, max_length=200)
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
def publish(self):
self.published_date=timezone.now()
self.save()
def __str__(self):
return self.ctext
I guess the logic in views is going wrong somewhere as it been shown while debugging
views.py
def post_list(request):
posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
post = get_object_or_404(Post, title=title)
cform = CommentForm()
comments = Comment.objects.all()
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 = post
new_comment.save()
#cform = CommentForm(request.GET)
#data = {}
#Comment.ctext(**data)
#if cform.is_valid():
#comment={}
#comment['ctext'] = request.POST['ctext']
#cform.changed_data['comment_auth'] = request.user
#cform['comment_auth'] = request.user
#cform['comment_auth_id_id'] = request.user
#cform.save()
return render(request, 'users/post_list.html', {'posts': posts, 'comments': comments, 'form': cform})
else:
form = CommentForm()
return render(request, 'users/post_list.html', {'posts': posts, 'comments': comments, 'form': cform})
Template
<div>
<h2>{{ post.title }}</h2>
<p>{{ post.text|linebreaksbr }}</p>
{{image.imgfile.url}}
{% if post.imgfile %}
<img src="{{ post.imgfile.url }}" alt="{{ post.imgfile.url }}">
{% endif %}
<p>By:- {{ post.author }}</p>
<p>published: {{ post.published_date }}</p>
<form method="POST" class="post-form" action="/users/post/list">{% csrf_token %}
{{ form }}
{% for comment in post.comment_set.all %}
<p><b>Comments: </b></p>
<p><b>{{ comment.comment_auth }}: </b>{{ comment.ctext }}</p>
{% endfor %}
<button type="submit" class="save btn btn-default">Comment</button>
</form>
</div>
i am assuming that you have post and comment entries already trough /admin and that you are able to fetch your posts, according to your question here would be the simpliest way to fetch your post-related comments:
{% for post in posts %}
<div>
{{ post.title }}
By - {{ post.author }}
{% for comment in post.comment_set.all %}
<-- is now looking up for all comment entries, where this post is the set foreignkey -->
<p><b>Comments: </b></p>
<p><b>{{ comment.comment_auth }}: </b>{{ comment.ctext }}</p>
{% endfor %}
</div>
{% endfor %}
views.py:
def post_list(request):
posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
context = {'posts': posts}
return render(request, 'users/post_list.html', context)
See django docs
change views.py as follows:
def post_list(request):
posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
comment_form = CommentForm()
comments = Comment.objects.all()
if request.method == "POST":
data = {
'ctext': request.POST['ctext'],
'comment_auth_id': request.user.id,
'title_id': request.POST['title_id']
}
comment_form = CommentForm(data=data)
if comment_form.is_valid():
Comment.objects.create(**data)
return render(request, 'users/post_list.html', {
'posts': posts,
'comments': comments,
'form': comment_form
})
else:
return render(request, 'users/post_list.html', {
'posts': posts,
'comments': comments,
'form': comment_form
})
As I can understand you are trying to post a new comment and your way of doing it is totally wrong and that is the reason why you are not able to show comments. This is the right way to do:
html:
<p>{{ err_msg }}</p>
{% for post in posts %}
<div>
<!-- Other elements -->
<form method="POST" action="/users/post/list">{% csrf_token %}
{{ form }}
<!-- Sending id of post you are commenting on -->
<input type="hidden" name="post_id" value="{{post.id}}">
<button type="submit">Comment</button>
</form>
<p><b>Comments: </b></p>
{% for comment in post.comment_set.all %}
<p><b>{{ comment.comment_auth }}: </b>{{ comment.ctext }}</p>
{% endfor %}
</div>
{% endfor %}
views.py:
def post_list(request):
posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
# I have removed the comments.objects.all
# as you getting them with post.comment_set.all in your template
form = CommentForm()
data = {'posts': posts, 'form': form}
if request.method == "POST":
# get the id of post you are commenting on
cur_post_id = request.POST.get('post_id')
try:
# check if the post exists in the database.
post = Post.objects.get(id=cur_post_id)
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.title= post
new_comment.comment_auth = request.user
new_comment.save()
except:
data['err_msg'] = 'Post does not exist!'
return render(request, 'users/post_list.html', data)
I've come to the end of the DjangoGirls tutorial and have been trying to add some extra functionality of my own
I have a new model called Subject. Each blog post now has a subject such as cookery, gardening, astrophysics, general, etc. When a blogger writes a new post, I want to force the Subject dropdown to default to 'General', but my template (post_edit.html) doesn't give me access to the SELECT so I can't set a default value
post_edit.html:
{% extends 'blog/base.html' %}
{% block content %}
<div>
<h1>New post</h1>
<form method="POST" class="post-form">{% csrf_token %}
{% if form.non_field_errors %}
<ul>
{% for error in form.non_field_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% for field in form %}
<div id="{{ field.auto_id }}_container"> {{ field.help_text }}
<div>
<span class="staticdata">{{ field.label_tag }}</span>
<span class="staticdata">{{ field }}</span>
</div>
<div id="{{ field.auto_id }}_errors">
{{ field.errors }}
</div>
</div>
{% endfor %}
<button type="submit" class="save btn btn-default">Save</button>
</form>
</div>
{% endblock %}
forms.py
from django import forms
from .models import Post, Subject
from django.contrib.auth.models import User
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'text', 'subject', 'author')
models.py
from django.db import models
from django.utils import timezone
class Post(models.Model):
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
subject = models.ForeignKey('Subject')
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
class Subject(models.Model):
description = models.CharField(max_length=200)
def __str__(self):
return self.description
views.py
from django.shortcuts import render, get_object_or_404
from django.shortcuts import redirect
from django.contrib.auth.models import User
from django.contrib.auth import authenticate,logout,login
from django.utils import timezone
from .models import Post, Subject
from .forms import PostForm
def post_list(request):
posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('-published_date')
return render(request, 'blog/post_list.html', {'posts': posts})
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
return render(request, 'blog/post_detail.html', {'post': post})
def post_new(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.published_date = timezone.now()
post.save()
return redirect('post_detail', pk=post.pk)
else:
form = PostForm()
return render(request, 'blog/post_edit.html', {'form': form})
def post_edit(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = PostForm(request.POST, instance=post)
if form.is_valid():
post = form.save(commit=False)
#... post.author = request.user
post.published_date = timezone.now()
post.save()
return redirect('post_detail', pk=post.pk)
else:
form = PostForm(instance=post)
return render(request, 'blog/post_edit.html', {'form': form})
def subject_list(request):
subjects = Subject.objects.all().order_by('description')
return render(request, 'blog/subject_list.html', {'subjects': subjects})
How can I set a default value for this so that the dropdown is populated with a default option when the user navigates to the page?
Try setting an initial value, in the following way
def post_new(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.published_date = timezone.now()
post.save()
return redirect('post_detail', pk=post.pk)
else:
# Get the default subject.
# Perhaps you want to give your subject a name, or use an id here,
# but assuming your default subject's description is 'General' for
# this example
default_subject = Subject.objects.get(description="General")
# Set the default value
form = PostForm(initial={"subject":default_subject})
return render(request, 'blog/post_edit.html', {'form': form})