I have a comment form for my posts. It looks like this
view.py
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.post = post
comment.author = request.user
#comment.author.photo = object.author.profile.image.url
comment.save()
return redirect('Post-detail', pk=post.pk)
else:
form = CommentForm()
return render(request, 'blog/add_comment_to_post.html', {'form': form})
models.py
class Comment(models.Model):
post = models.ForeignKey('blog.Post', on_delete=models.CASCADE, related_name='comments')
author = models.CharField(max_length=20)
text = models.TextField(max_length=200, verbose_name='内容')
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
forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('text',)
widgets = { #size of textbox
'text': forms.Textarea(attrs={'rows':4}),
}
Where should I add pagination function to my comments to make it works?
I have a pagination for my posts, but posts are using a DetailView class and I dont know how to make it work for comment function
Basically you need to do that in Post-detail view. As DetailView does not provide pagination, so you need to provide them on your own. You can override the get context method. For example:
from django.core.paginator import Paginator
class PostDetailView(DetailView):
...
def get_context_data(self):
context = super(PostDetailView, self).get_context_data()
_list = Comment.objects.filter(post=self.kwargs.get('pk'))
paginator = Paginator(_list, 25) # Show 25 contacts per page
page = request.GET.get('page')
context['comments'] = paginator.get_page(page)
return context
And render comments in Template like this:
<div class="pagination">
<span class="step-links">
{% if comments.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ comments.number }} of {{ comments.paginator.num_pages }}.
</span>
{% if contacts.has_next %}
next
last »
{% endif %}
</span>
</div>
More information can be found in documentation as well.
This is how it can be done.
from rest_framework.pagination import PageNumberPagination
# Create a custom pagination class.
class CommentPagination(PageNumberPagination):
page_size = 1000
page_size_query_param = 'page_size'
max_page_size = 10000
# Define the Pagination class in PostDetailView.
class PostDetailView(DetailView):
pagination_class = CommentPagination
# This is how api can be called.
# something.com/comment/?page=4
Related
I am following the guide to create comment given by Django central, https://djangocentral.com/creating-comments-system-with-django/ and it is working. However I am using the {{ form.as_p }} Which will give 3 fields, as the form say, with name, email and the body. But i wanted to have predefined name, which would be your username you are logged inn with and the email attached to that account. How would i go ahead to create that?
forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['name', 'email', 'body']
models.py
class Comment(models.Model):
post = models.ForeignKey(Post, related_name='comments', on_delete=models.CASCADE)
name = models.CharField(max_length=255)
email = models.EmailField()
body = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-date_added']
def __str__(self):
return self.name
views.py
def post_detail(request, category_slug, slug, status=Post.ACTIVE):
post = get_object_or_404(Post, slug=slug)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect('post_detail', category_slug=category_slug, slug=slug)
else:
form = CommentForm()
return render(request, 'blog/post_detail.html', {'post': post, 'form': form})
in the html template
{% if user.is_authenticated %}
<h2 class="subtitle is-4">Comments</h2>
<form method="post" class="mb-6">
{% csrf_token %}
{{ form.as_p }}
<div class="field">
<div class="control">
<button class="button is-success">Submit comment</button>
</div>
</div>
</form>
{% endif %}
If you want to pre-set the username and email fields, you can use the initial form parameters like this:
views.py
def post_detail(request, category_slug, slug, status=Post.ACTIVE):
post = get_object_or_404(Post, slug=slug)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect('post_detail', category_slug=category_slug, slug=slug)
else:
user = request.user
form = CommentForm(initial={"name": user.username, "email": user.email})
return render(request, 'blog/post_detail.html', {'post': post, 'form': form})
forms.py
class CommentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].disabled = True
self.fields['email'].disabled = True
# OR set readonly widget attribute.
# self.fields['name'].widget.attrs['readonly'] = True
# self.fields['email'].widget.attrs['readonly'] = True
class Meta:
model = Comment
fields = ['name', 'email', 'body']
I am new to Django and as such I have a few gaps in my knowledge, particularly with regards to the get_context_data() in views. My aim is to try and make use of the DRY aspect of Django so that I have a list of categories on my webpage and each category reveals a list of posts that fall under it. To achieve this, I have a ListView and DetailView class (shown below). Note, the categories and posts are all objects of the same model.
My issues are the following:
I would like to display only some of the posts in my ListView not all of them. Is there a dynamic way to do this?
In my DetailView, I want to display a list of posts but from a single choice, for eg say choice1 is selected when creating a post, then only all posts with choice 1 selected will be displayed. I would like to do that dynamically, currently its hardcoded to only display choice1. I have tried to use the get_context_data() to do this but this only outputs a queryset in my template which leads me to problem 3
My HTML file currently displays all posts of choice1 as a QuerySet but not the actual content
Views.py
class PostListView(ListView):
model = Post
template_name = 'blog/post.html'
context_object_name = 'posts'
ordering = ['-date_posted']
class PostDetailView(DetailView):
model = Post
def get_context_data(self, *args, **kwargs):
context = super(PostDetailView, self).get_context_data(*args, **kwargs)
list_of_relevant_articles = Post.objects.filter(choice = "choice1")
context.update({'list_of_relevant_articles': list_of_relevant_articles})
return context
Models.py
POST_CHOICES = (
('choice1','CHOICE1'),
('choice2', 'CHOICE2'),
('choice3','CHOICE3'),
('choice4','CHOICE4'),
)
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)
image = models.ImageField(blank=True, null=True)
choice = models.CharField(max_length=14, choices = POST_CHOICES, default ='choice1')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog-post', kwargs={'pk': self.pk})
Html file
{% block content %}
<!-- This for loop does not output anything but I believe i need one to iterate through
a queryset-->
{% for post in object_list %}
{{ list_of_relevant_articles }}
{% endfor %}
{% endblock content %}
Any help/advice will be greatly appreciated
1
you can use context['posts'] = context['posts'][:3] and a normal loop in template, but it is better to use pagination in general.
class PostListView(ListView):
model = Post
template_name = 'blog/post.html'
context_object_name = 'posts'
ordering = ['-date_posted']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['posts'] = context['posts'][:3]
return context
{% for post in posts %}
{{ post }} <br>
{% endfor %}
The other way is do not use get_context_data and inside the template use slicing like:
{% for post in posts|slice:":2" %}
2, 3
class PostDetailView(DetailView):
model = Post
template_name = 'blog/detail.html'
slug_url_kwarg = "choice"
slug_field = "choice"
context_object_name = 'posts'
def get_object(self, queryset=None):
return Post.objects.filter(choice=self.kwargs.get("choice"))
{% for post in posts %}
{{ post }} <br>
{% endfor %}
urls.py
urlpatterns = [
# ...
path('post/', views.PostListView.as_view(), name='list'),
path('post/<str:choice>/', views.PostDetailView.as_view(), name='detail'),
]
so in the browser: .../post/choice2/, .../post/choice3/, ...
I am working on a movie website with Django and I'm trying to add user comments. Only the registered users should be allowed to comment. I have the following so far:
models.py:
class Comment(models.Model):
movie = models.ForeignKey(Movie, related_name = "comments", on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete = models.CASCADE)
content = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '%s - %s' % (self.movie.title, self.name)
forms.py
class CommentForm(forms.ModelForm):
content = forms.CharField(label ="", widget = forms.Textarea(
attrs ={
'class':'form-control',
'placeholder':'Comment here!',
'rows':5,
'cols':50
}))
class Meta:
model = Comment
fields =['content']
views.py
class MovieDetailsView(generic.DetailView):
model = Movie
template_name = "details.html"
def comment(request, id):
movie= Movie.objects.get(id)
if request.method == 'POST':
cf = CommentForm(request.POST or None)
if cf.is_valid():
content = request.POST.get('content')
comment = Comment.objects.create(movie = movie, user = request.user, content = content)
comment.save()
return redirect(movie.get_absolute_url())
else:
cf = CommentForm()
context ={
'comment_form':cf,
}
return render(request, 'details.html', context)
details.html
<form method="POST" action="#" class="form">
{% csrf_token %}
{{comment_form.as_p}}
<button type="submit" class="btn btn-primary btn-lg">Submit</button>
<textarea id="text" name="text" class="form__textarea" placeholder="Add comment"></textarea>
<button type="button" class="form__btn">Send</button>
</form>
The MovieDetailsView displays the details page of the movie and has a comment section. However, when I submit the comment, it simply displays a white page and this link: http://127.0.0.1:8000/details/1# . The comment is not saved on the database and I can't seem to find what the issue is. I am also following the example from this link.
Thanks in advance!
You can combine FormMixin with DetailView - Using FormMixin with DetailView.
from django.urls import reverse
from django.views.generic.detail import DetailView
from django.views.generic.edit import FormMixin
from django.http import HttpResponseForbidden
class MovieDetailsView(FormMixin, DetailView):
model = Movie
template_name = "details.html"
form_class = CommentForm
def get_success_url(self):
return reverse('movie_detail', kwargs={'pk': self.object.pk})
# or
# return self.object.get_absolute_url()
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)
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.movie = self.object
form.save()
return super().form_valid(form)
In the post() method, we first check if the user is logged in:
if not request.user.is_authenticated:
return HttpResponseForbidden()
In template:
<form method="POST" class="form">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary btn-lg">Submit</button>
</form>
You can remove the action attribute, the form will be submitted to the same page.
I'm building a post-comment model in one view, one page, something like facebook. I have two forms in my home.html and view.py: new post and new comment. In each post container, there is a new comment form.
I have a problem because I don't know how to relate comment to post - specifically how to pass post.id to my comment form.
Is it possible to pass my {{ post.id }} to my {{newCommentForm.field }}? That each comment has a default value of post's id?
My home.html:
{% for post in posts %}
<div class="container">
<a class="user" href="#">{{ post.author }}</a>, {{ post.date_posted }}
<img src="{{ post.author.profile.image.url }}" alt="{{ post.author }}"style="width:100%;">
<p>{{ post.content }}</p>
<form METHOD="POST" class="new_post">
{% csrf_token %}
{{ newCommentForm.content }}
{{ newCommentForm.post }}
<button type="submit" name="newCommentSubmit">Add</button>
</form>
</div>
{% endfor %}
models.py
class Post(models.Model):
content = models.TextField(max_length=1000)
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.content
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField(max_length=500)
date = models.DateTimeField(default=timezone.now)
def add_coment(self):
self.date = timezone.now()
self.save()
def __str__(self):
return self.content
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
def __str__(self):
return f'{self.user.username}'
views.py
#login_required()
def home(request):
newPostForm = newPost()
newCommentForm = newComment()
if request.method == 'POST':
if 'newPostSubmit' in request.POST:
newPostForm = newPost(request.POST, prefix='newpost')
if newPostForm.is_valid():
instance = newPostForm.save(commit=False)
instance.author = request.user
instance.date_posted = timezone.now()
instance.save()
newCommentForm = newComment(prefix='newcomment')
elif 'newCommentSubmit' in request.POST:
newCommentForm = newComment(request.POST, prefix='newcomment')
if newCommentForm.is_valid():
instance = newCommentForm.save(commit=False)
instance.author = request.user
instance.date_posted = timezone.now()
instance.save()
newPostForm = newPost(prefix='newpost')
else:
newPostForm = newPost(prefix='newpost')
newCommentForm = newComment(prefix='newcomment')
context = {
'newPostForm': newPostForm,
'newCommentForm': newCommentForm,
'posts': Post.objects.all().order_by('-date_posted'),
'comments': Comment.objects.all()
}
return render(request, 'blog/home.html', context)
def about(request):
return render(request, 'blog/about.html')
My model is working now, comments are added, but I need to choose my post (post.id) manually from the default dropdown field witch all posts.
Add your post to comment like this:
if newCommentForm.is_valid():
instance = newCommentForm.save(commit=False)
instance.author = request.user
instance,=.post = request.post
instance.date_posted = timezone.now()
instance.save()
And send post in request!
I wanted to pass two objects to templates, so I put theme in the context and try to send them but failed. I tried few times but it seems like user get objects but comment is always empty even though there are some data in db.
This is views.py
#login_required
def detail(request, article_no):
if not request.user.is_authenticated():
return redirect_to_login(next, 'blog/login.html')
else:
user = request.user
if 'username' in request.session:
username = request.session['username']
item = get_object_or_404(Article, pk=article_no)
item.hit = Article.objects.filter(pk=article_no).update(hit = item.hit+1)
#comment = Comment.objects.filter(pk=article_no).order_by('comment_no')
comment = Comment.objects.all()
context = {
'item': item,
'comment': comment,
}
return render(request, 'blog/detail.html', context)
this is models.py
from datetime import datetime
from django.db import models
from django.template.defaultfilters import default
class Article(models.Model):
no = models.AutoField(primary_key=True)
title = models.CharField(max_length=50)
content = models.CharField(max_length=300)
writer = models.CharField(max_length=50)
is_visible = models.BooleanField(default=True)
created_date = models.DateTimeField(editable=False, default=datetime.now())
hit = models.IntegerField(default=1)
class Comment(models.Model):
article_no = models.IntegerField(default=1)
comment_no = models.AutoField(primary_key=True)
comment_writer = models.CharField(max_length=50)
comment_body = models.CharField(max_length=300)
comment_date = models.DateTimeField(editable=False, default=datetime.now())
template
{{ item.title }}
{{ item.no }}
{{ item.writer }}
{{ item.hit }}
{{ item.created_date }}
{{ item.content }}
{% if comments %}
{% for comment in comments %}
{{ comment.comment_no }}
{{ comment.comment_writer }}
{{ comment.comment_body }}
{{ comment.comment_date }}
{% endfor %}
{% else %}
<div class="row">no comment.</div>
{% endif %}
The variable you're passing from the view with all the comments in is called comment, for some reason. But in the template you try to iterate over comments, which doesn't exist. You should use consistent naming; it would make more sense to use comments.
Your code
#login_required
def detail(request, article_no):
if not request.user.is_authenticated():
return redirect_to_login(next, 'blog/login.html')
else:
user = request.user
if 'username' in request.session:
username = request.session['username']
item = get_object_or_404(Article, pk=article_no)
item.hit = Article.objects.filter(pk=article_no).update(hit = item.hit+1)
#comment = Comment.objects.filter(pk=article_no).order_by('comment_no')
comment = Comment.objects.all()
context = {
'item': item,
'comment': comment,
}
return render(request, 'blog/detail.html', context)
Correction Code -->
#login_required
def detail(request, article_no):
if not request.user.is_authenticated():
return redirect_to_login(next, 'blog/login.html')
else:
user = request.user
if 'username' in request.session:
username = request.session['username']
item = get_object_or_404(Article, pk=article_no)
item.hit = Article.objects.filter(pk=article_no).update(hit = item.hit+1)
#comment = Comment.objects.filter(pk=article_no).order_by('comment_no')
comment = Comment.objects.all()
return render(request, 'blog/detail.html', {'item':item,
'comment':comment})
Details will be found here (pass multiple objects to RequestContext in django)
And in the template you use comments but in your code, there are no comments, rather then it is a comment.