delete and modify comments by users - django

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()

Related

local variable 'comment_form' referenced before assignment in django

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.

Django song not being added to 'favourite songs'

I want my users to be able to add certain songs to Favourite Songs but although the success message 'Added to favourite songs' but when I visit the Favourite Songs page, I see no songs there. How can I fix this? Thanks in advance!
My models.py:
class Songs(models.Model):
title = models.CharField(max_length = 100)
lyrics = models.TextField()
author = models.CharField(max_length = 100)
track_image = models.CharField(max_length=2083)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('/', kwargs={'pk': self.pk})
My views.py:
def home(request):
context = {
'songs': Songs.objects.all()
}
return render(request, 'home.html', context)
#login_required
def add_to_fav_songs(request, **kwargs):
fav_song = Songs.objects.filter(id=kwargs.get('id'))
messages.success(request, f'Added to favourite songs')
return redirect('/')
class Fav_songs(ListView):
model = Songs
template_name = 'fav_songs.html'
context_object_name = 'fav_song'
paginate_by = 2
def get_queryset(self):
return Songs.objects.filter(pk=self.kwargs.get('pk'))
My favoutie_songs.html:
{% for song in fav_song %}
<article class="media content-section">
<div class="media-body">
<h2><a class="article-title" href="{% url 'song-detail' song.id %}">{{ song.title }}</a></h2>
<div class="article-metadata">
<a class="mr-2" href="{% url 'author-songs' song.author %}">{{ song.author }}</a>
</div>
<br>
<img class="card-img-bottom" height="339px" width="20px" src="{{ song.track_image }}">
</div>
</article>
{% endfor %}
Your Song is not connected to the User, so you never keep track about what user has wat song as favorite.
You should add a ManyToManyField to your Song model with:
from django.conf import settings
class Songs(models.Model):
# …
favorited_by = models.ManyToManyField(
settings.AUTH_USER_MODEL,
related_name='favorite_songs'
)
# …
Then in our view we can add the logged in user to the favorited_by field. Since we here alter data, this should be done with a POST request:
from django.contrib.auth.decorators import loginrequired
from django.shortcuts import get_object_or_404
from django.views.decorators.http import require_http_methods
#login_required
#require_POST
def add_to_fav_songs(request, pk):
fav_song = get_object_or_404(Songs, id=pk)
fav_song.favorited_by.add(request.user)
messages.success(request, 'Added to favourite songs')
return redirect('/')
For the ListView, we can then filter by the logged in user:
from django.contrib.auth.mixins import LoginRequiredMixin
class Fav_songs(LoginRequiredMixin, ListView):
model = Songs
template_name = 'fav_songs.html'
context_object_name = 'fav_song'
paginate_by = 2
def get_queryset(self):
return Songs.objects.filter(favorited_by=self.request.user)
You should change the button to add this to the favorite to a miniform:
<form method="post" action="{% url 'add-to-fav-songs' song.id %}">
<button class="btn btn-danger" type="submit">Add to Favorite Songs</button>
</form>
Note: You can limit views to a class-based view to authenticated users with the
LoginRequiredMixin mixin [Django-doc].

get() returned more than one Comment -- it returned 2

I was trying to add a new post and I got the error code in the title. It seems that when I click on a post with one comment it shows the detail page but if it has more than one I get the error. I think it has something to do with my detail view. I have been changing the kwargs in my comment variable and I have been getting different errors thats why I think it has to do with the detail view.
views.py
from django.shortcuts import render, get_object_or_404, redirect
from .models import Post, Comment
from django.utils import timezone
from .forms import PostForm
def post_index(request):
posts = Post.objects.filter(published_date__lte=timezone.now()).order_by("published_date")
return render(request, "blog/post_index.html", {"posts" : posts})
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
comment = get_object_or_404(Comment, post=post.pk)
return render(request, "blog/post_detail.html", {"post" : post, "comment" : comment})
def post_create(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect("post_detail", pk=post.pk)
else:
form = PostForm()
return render(request, "blog/post_create.html", {"form" : form})
models.py
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=250)
text = models.TextField()
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.title
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
author = models.CharField(max_length=250)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
post_detail.html
{% extends 'blog/base.html' %}
{% block content %}
<div class="post">
{% if post.published_date %}
<div class="date">
{{ post.published_date }}
</div>
{% endif %}
<h2>{{ post.title }}</h2>
<p>{{ post.text|linebreaksbr }}</p>
</div>
<hr>
{% 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 %}
{% endblock %}
Don't use get_object_or_404, it would return only one comment, instead just filter out your comments by
comment.objects.filter(post=pk)

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

Using Django, how do I set a default value for a foreign key select when creating a new item

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})