Django: How to add comments under post - django

I have trouble adding comments under my posts on the website I'm creating using Django.
This is my story.html file, which is supposed to show the story title, the story itself, all the comments of the story and give users the ability to add a new comment. Although the form is shown, it is not usable. Even though I have added comments to the stories manually through admin, none of them is shown.
{% extends "pinkrubies/base.html" %}
{% block content %}
<div class="post-preview">
<h2 class="post-title"> {{ story.title }}</h2>
<p class="post-subtitle">
{{ story.story }}
</p>
</div>
<div class="post-preview">
{% for com in latest_comments %}
<div class="post-preview">
<p class="post-subtitle"> {{ comment.com }} </p>
</div>
{% endfor %}
</div>
{% if user_id %}
<div class="post-preview">
<form action="{% url 'pinkrubies:story' user.id story.id %}" method="post">
{% csrf_token %}
<div class="form-group">
<p class="post-title">
Comments
</p>
<textarea id="text" name="text"class="form-control" placeholder="Comment" rows="4">{{ comment.com }}
</textarea>
</div>
<button type="submit" class="btn btn-primary"> Submit </button>
</form>
</div>
{% else %}
<p class="post-meta">You must have an account to comment. Log in or Register</p>
{% endif %}
{% endblock %}
views.py
def story_view(request, user_id, story_id):
latest_comments = Comment.objects.order_by('-date')
if story_id is not None:
story = get_object_or_404(Story, pk=story_id)
else:
story = Story()
story.user_id = user_id
if request.method == 'POST':
story.title = request.post.get('title')
story.story = request.post.get('story')
story.date = timezone.now()
story.save()
return HttpResponseRedirect(reverse('pinkrubies:story', args=(user_id,)))
else:
context = {
'user_id': user_id,
'story_id': story_id,
'title': story.title,
'story': story,
'comments': story.comments,
'latest_comments': latest_comments
}
return render(request, 'pinkrubies/story.html', context)
def comment_view(request, comment, user_id):
latest_comments = Comment.objects.order_by('-date')
if request.method == 'POST':
comment.com = request.POST['com']
comment.date = timezone.now()
comment.save()
return HttpResponseRedirect(reverse('pinkrubies:story', args=(user_id,)))
else:
context = {'latest_comments': latest_comments}
return render(request, 'pinkrubies/story.html', context)
I am aware I have added the "latest_comments" in both views, I did that to see if any of it works and it doesn't. I'm not even sure that the comment_view is needed.
Also, when I try to submit a comment, this error is thrown:
AttributeError: 'WSGIRequest' object has no attribute 'post'
in story_view story.title = request.post.get('title')
Any help is welcome!
Thank you !!!

First of all, if you want to get something from POST, you should use request.POST.get('title'); although I would rather use a Form and let it handle the request.
At a first glance, this:
{% for com in latest_comments %}
<div class="post-preview">
<p class="post-subtitle"> {{ comment.com }} </p>
</div>
{% endfor %}
Sould be
{% for comment in latest_comments %}
<div class="post-preview">
<p class="post-subtitle"> {{ comment.com }} </p>
</div>
{% endfor %}
In your code, you're using com to iterate through latest_comments, yet you try to use comment to access com attribute (not sure how your Comment model looks like though)

It's accessed via request.POST. WSGIRequest object does not have a post field it has POST field.
Also, you are not sending the fields you are attempting to read from request.
...
story.title = request.POST.get('title')
story.story = request.POST.get('story')
...
only field you are sending is called text and it should be accessed like this
text = request.POST.get('text')
also template has more errors please check your code once again.

Related

How to delete a record through a button in django

I have created a model Announcement in models.py file
class Announcement(models.Model):
title = models.CharField(max_length=30)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE)
def __str__(self):
return self.title
And for deleting a record of Announcement I have created the following view
def AnnouncementDelete(request, pk):
announcement = get_object_or_404(Announcement, pk=pk)
if request.method=='POST':
announcement.delete()
return redirect('/')
return render(request, 'classroom/announcement_confirm_delete.html', {'announcement': announcement})
The delete view of announcement (that is AnnouncementDelete) has the following url
path("delete/<int:pk>/", view=views.AnnouncementDelete, name="AnnouncementDelete"),
If i enter
http://127.0.0.1:8000/classroom/delete/3
on browser it is deleting the Announcement having pk = 3
Now I want a button to directly delete my record without the need of typing http://127.0.0.1:8000/classroom/delete/3 on browser
I have tried the following methods in my allannouncement.html file
{% extends "classroom/base.html" %}
{% block content %}
<h1>Announcements</h1>
{% for announcement in announcements %}
<!-- starting loop (posts is keyword from view) -->
<div style="border-style: solid;">
{% if announcement.teacher.user == request.user %}
<div>
Delete
</div>
{% endif %}
{{ announcement.pk }}
<a class="mr-2">Posted by: {{ announcement.teacher }}</a>
<h2><a class="article-title">{{ announcement.title }}</a></h2>
<p class="article-content">{{ announcement.content}}</p>
</div>
{% endfor %}
{% endblock content %}
but it is giving the following error
NoReverseMatch at /classroom/allannouncement/
Reverse for 'AnnouncementDelete' with no arguments not found. 1 pattern(s) tried: ['classroom/delete/(?P<pk>[0-9]+)/$']
then I also tried passing pk with url like
Delete
But it is giving the following error
TemplateSyntaxError at /classroom/allannouncement/
Could not parse the remainder: ',' from ','
Remove common #refer this
Delete
instead of a link add a form.
Add
{% load crispy_forms_tags %}
in your html file and then add
{% if announcement.teacher.user == request.user %}
<div>
<form action="{% url 'classroom:AnnouncementDelete' announcement.id %}"
method="post">
{% csrf_token %}
<input type="submit" value="Delete">
</form>
</div>
{% endif %}

i cant specify a user to access my post in django

its a bit tricky for i tried solving it but cant,i am using class based views with customuser model, i have a blog post with multiple user i want to make it in a way only the main authour of a post should be allowed to delete or update post but i dont seems to know what to do or how to set it,here is my code kindly take a glance and release me from debugging,instead i get what i want wont get nothing and inatead i get something every author has the right to delete each other's post plus this is how i want it to look like when the user view its blog_detail but for non user it should be blank [![enter image description here][1]][1]
views.py
class BlogUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Blog
fields = ['title', 'categories', 'overview', 'thumbnail', 'summary']
def form_valid(self, form):
form.instance.user = Doctor.objects.get(user=self.request.user)
return super().form_valid(form)
def test_func(self):
blog = Doctor.objects.get(user=self.request.user)
if self.request.user == blog.user:
return True
return False
class BlogDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Blog
success_url = reverse_lazy('blog')
def test_func(self):
blog = Doctor.objects.get(user=self.request.user)
if self.request.user == blog.user:
return True
return False
blog_detail.html
{% extends "pages/base.html" %}
{% load static %}
{% block title %}Jazeera Blog{% endblock title %}
{% block meta %} the best medical-service website in Abuja {% endblock meta %}
{% block content %}
<br><p></p>
<section class="ftco-section bg-light" id="blog-section">
<div class="container">
<div class="row justify-content-center mb-5 pb-5">
<div class="col-md-10 heading-section text-center ftco-animate">
<h2 class="mb-4">Gets Every Single Updates Here</h2>
<p>Far far away, behind the word mountains, far from the countries Vokalia and Consonantia</p>
</div>
</div>
<div class="container">
<div class="row justify-content-center mb-5 pb-5">
<div class="col-lg-8 ftco-animate">
<h2 class="text-center">{{ object.title }}</h2>
<div class="meta mb-3 text-center">
<div><h6><span><a href = "">written By {{ object.user }}</span><small class="date"><i class="icon-clock"></i> {{ object.timestamp|timesince }} ago</small><a/></h6>
</div>
</div>
<div><small class="icon-eye text-danger">{{ object.view_count }}</small></div>
<div class="meta mb-3 text-center">
<h5>{% for cat in object.categories.all %}<span class="btn btn-dark">{{ cat }}</span> {% endfor %}</h5>
</div>
<p class="text-center">{{ object.overview }}</p>
{% if object.created_by == user %}
<a class="btn btn-secondary btn-sm mt mb-2" href="{% url 'blog-update' blog.id %}">Update</a>
<a class="btn btn-danger btn-sm mt mb-2" href="{% url 'blog-delete' blog.id %}">Delete</a>
{% endif %}
</div>
</div>
</div>
</div>
</section>
{% endblock content %}
##urls.py
path('blog/', BlogListView.as_view(), name='blog'),
path('blog/<int:pk>/', BlogDetailView.as_view(), name='blog-detail'),
path('blog/<int:pk>/update/', BlogUpdateView.as_view(), name='blog-update'),
path('blog/new/', BlogCreateView.as_view(), name='blog-create'),
path('blog/<int:pk>/delete/', BlogDeleteView.as_view(), name='blog-delete'),
[1]: https://i.stack.imgur.com/aojDX.jpg
You can restrict the set of Blog objects in the queryset to the ones that are written by the request.user:
class BlogDeleteView(LoginRequiredMixin, DeleteView):
model = Blog
success_url = reverse_lazy('blog')
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
user=self.request.user
)
This will return a HTTP 404 error, if a user aims to remove a Blog object post where the post.user is not the logged in user.
In your views (BlogUpdateView and BlogDeleteView), update test_func to become;
def test_func(self):
blog = Doctor.objects.get(pk=self.kwargs.pk)
return self.request.user == blog.user
If you're testing for one condition in multiple views as you just did, it will be better creating a separate class for the test. Say, IsOwnerMixin where you perform the test and then inherit it in the required view(s).
Additionally, you can change {% if object.user == user %} in your blog_detail.html to become {% if user.is_authenticated and object.user == request.user %}
Good luck!

How can a page stay where it is instead of refreshing and going to the top after interact with a domestic link such as 'edit'?

So I've built a Django forum, just added in the comment function. Here's a minor annoying thing needs to be sorted...
Problem:
When editing a comment, after pressing edit button/link, the page naturally refreshes itself, going to the top of the page instead of, what I hope for, staying right where it was... This isn't nice or friendly at all...
Here's the code:
url.py
app_name = 'forum'
urlpatterns = [
path('topic_<int:topic_pk>/<int:post_pk>/edit_<int:comment_pk>/', views.post, name='edit_comment'),
]
views.py
def post(request, topic_pk, post_pk, comment_pk=None):
'''displaying one post under a topic and all the comments under this post'''
# get existing topic and post
topic = get_object_or_404(Topic, pk=topic_pk)
try:
post = topic.post_set.get(pk=post_pk)
except:
raise Http404
# get all the comments under this post
comments = post.comment_set.order_by('-pk')
# deal with comment editing
if comment_pk != None:
comment_to_be_edited = get_object_or_404(Comment, pk=comment_pk)
if 'delete' in request.POST:
comment_to_be_edited.delete()
return redirect('forum:post', topic_pk, post_pk)
# get form with existing data ready to be rendered or edited/updated
edit_comment_form = CommentForm(instance=comment_to_be_edited)
if 'update' in request.POST:
edit_comment_form = CommentForm(instance=comment_to_be_edited, data=request.POST)
if edit_comment_form.is_valid():
edit_comment_form.save()
return redirect('forum:post', topic_pk, post_pk)
# if not to delete or to update, simply render the existing form with comment data ready to be edited
return render(request, 'forum/post.html', {
'topic': topic,
'post': post,
'comments': comments,
'comment_to_be_edited': comment_to_be_edited,
'edit_comment_form': edit_comment_form})
# if not posting a new comment, simply render the form
if request.method != 'POST':
form = CommentForm()
else:
# deal with posting a new comment
form = CommentForm(data=request.POST)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.post = post
new_comment.author = request.user
new_comment.save()
return redirect('forum:post', topic_pk, post_pk)
# render the post and all the comments and the empty/error form
return render(request, 'forum/post.html', {
'topic': topic,
'post': post,
'comments': comments,
'form': form})
post.html
{% for comment in comments %}
<div class="comment mt-3 border p-3 rounded-lg shadow-sm" id="{{ comment.pk }}">
{% if comment == comment_to_be_edited %}
<div class="d-flex justify-content-between align-items-start border-bottom">
<p class="font-weight-bold">{{ comment.author }}</p>
<p class="font-weight-light">{{ comment.date_added }}</p>
</div>
<form action="{% url 'forum:edit_comment' topic.pk post.pk comment.pk %}" method="POST">
{% csrf_token %}
{% bootstrap_form edit_comment_form %}
<button name="update" class="btn btn-sm btn-info">update</button>
<a href="{% url 'forum:post' topic.pk post.pk %}"
class="btn btn-sm btn-outline-danger">cancel</a>
<button name="delete" class="btn btn-sm btn-danger float-right">delete</button>
</form>
{% else %}
<div class="d-flex justify-content-between align-items-start border-bottom">
<p class="font-weight-bold">{{ comment.author }}</p>
<p class="font-weight-light">{{ comment.date_added }}</p>
{% if comment.author == user %}
<a href="{% url 'forum:edit_comment' topic.pk post.pk comment.pk %}"
class="shadow-sm btn btn-sm btn-outline-secondary float-right px-3">edit</a>
{% endif %}
</div>
<p class="mt-4">{{ comment.content | linebreaks }}</p>
{% endif %}
</div>
{% empty %}
<p class="text-muted">No comment for this post yet. Anything you'd like to share?</p>
{% endfor %}
You can probably tell from the code that I'm quite new in programming.
Now I've added an id="{{ comment.pk }}'' for each comment div based on their primary key. I'm probably v close to the solution, yet I'm not sure how to reference a div id in the view function...
Solution I've tried:
If only I could just add in #<int:comment_pk> at the end the each url when edit is triggered, like so:
path('topic_<int:topic_pk>/<int:post_pk>/edit_<int:comment_pk>/#<int:comment_pk>', views.post, name='edit_comment'),
or simply
path('topic_<int:topic_pk>/<int:post_pk>/edit/#<int:comment_pk>', views.post, name='edit_comment'),
However, Django doesn't seem to recognise the hash tag in urlpatterns, instead it's showing %20 something in rendered url of the page after edit is pressed/triggered.
I've also desperately tried this, hoping it'd work:
# view function
# --snip--
return render(request, 'forum/post.html#comment.pk', context)
Please help. Thanks!!
Refreshing page on form submission is browser's natural behavior.
What you want is Ajax read this article to learn how to Ajaxify your forms https://realpython.com/django-and-ajax-form-submissions/
However, I would advise using https://intercoolerjs.org/ for basic ajax views.

Django display search results

I'm currently working on a blog app in Django. As all Blog apps I need a search form. Therefore I have write a small view and context processor (to make the search form available globally) that queries search results:
view.py:
class BlogSearchListView(ListView):
model = Post
paginate_by = 10
def get_queryset(self):
qs = Post.objects.published()
keywords = self.request.GET.get('q')
if keywords:
query = SearchQuery(keywords)
title_vector = SearchVector('title', weight='A')
content_vector = SearchVector('content', weight='B')
tag_vector = SearchVector('tag', weight='C')
vectors = title_vector + content_vector + tag_vector
qs = qs.annotate(search=vectors).filter(search=query)
qs = qs.annotate(rank=SearchRank(vectors, query)).order_by('-rank')
return qs
base.html:
<div class="globalsearch">
<form id="searchform" action="{% url 'search' %}" method="get" accept-charset="utf-8">
<label for="{{ categorysearch_form.category.id_for_label }}">In category: </label>
{{ categorysearch_form.category }}
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search for ...">
<button class="searchbutton" type="submit">
<i class="fa fa-search"></i>
</button>
</form>
</div>
settings.py:
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'quickblog.quickblog.context_processors.categorysearch_form',
)
context_processors.py
from .forms import PostForm
def categorysearch_form(request):
form = PostForm()
return {'categorysearch_form': form}
post_list.html:
{% extends 'quickblog/base.html' %}
{% block content %}
{% for post in posts %}
<div class="post">
<h1><u>{{ post.title }}</u></h1>
<p>{{ post.content|linebreaksbr }}</p>
<div class="date">
<a>Published by: {{ post.author }}</a><br>
<a>Published at: {{ post.published_date }}</a><br>
<a>Category: {{ post.category }}</a><br>
<a>Tag(s): {{ post.tag }}</a>
</div>
</div>
{% endfor %}
<div>
<span>
{% if posts.has_previous %}
« First <a> |</a>
Previous
{% endif %}
{% if posts.has_next %}
<span> Page {{ posts.number }} of {{ posts.paginator.num_pages }}.</span>
Next<a> |</a>
Last »
{% endif %}
</span>
</div>
{% endblock %}
The rest of the project can be checked here: https://github.com/rsmvdl/quickblog
I now want to render the results the same way as they get display in my post_list.html view with one detail more: I want that the word the user searched for gets highlighted in the search results, so that the user is able to get the context of the content ASAP ;) . I hope to get any creative ideas. Thanks.
You can create a custom template filter to add an HTML tag around the searched-for word in your post title or content. This will not be an exact solution but you should be able to adapt it:
#register.filter
#stringfilter
def highlight_search_term(text, search_term):
if search_term in text:
text.replace(search_term, '<span class="highlight">' + search_term + '</span>')
return mark_safe(text)
You can then apply this filter to your post title or content in your template with {{ post.title|highlight_search_term:search_term }}.
You will need to also pass search_term to your template context so that the filter can know what to highlight. Be careful with mark_safe() if you are applying this to user-submitted content!
You can create a context processor and pass request.GET.get('q') as context.
context_processors.py
def search(request):
search_keyword = request.GET.get('q')
context = {'search_keyword': search_keyword}
return context
And in your template access it as {{ search_keyword }}
search_results.html
<h3>Showing results for: {{ search_keyword }}</h3>

Use two forms at the same time

I want to submit two forms in my template. The reason for that is, that I have two separate models, each with a model form, which I have to submit at the same time to create the desired result:
I have these two forms:
class BootstrapModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super(BootstrapModelForm, self).__init__(*args, **kwargs)
for field in iter(self.fields):
self.fields[field].widget.attrs.update({
'class': 'form-control'
})
class DeckForm(ModelForm):
class Meta:
model = Deck
exclude = ('dust',)
class GuideForm(BootstrapModelForm):
class Meta:
model = Guide
exclude = ('author', 'upvotes', 'downvotes', 'release_date', 'modified', 'deck')
def __init__(self, *args, **kwargs):
super(GuideForm, self).__init__(*args, **kwargs)
self.fields['title'].label = 'Title of your AWESOME Guide!'
self.fields['public'].label = 'Check, when you want to make your guide public'
self.fields['public'].widget.attrs.update({
'class': 'checkbox'
})
self.fields['introduction'].label = 'Introduction (not required, but recommended)'
A deck is a part of the Guide that gets created. Since the user should be able to create his own deck while writing his guide, I need two forms.
In my views I handled it like this:
def guide_create(request):
if request.method == "POST":
deck_form = DeckForm(request.POST)
guide_form = GuideForm(request.POST)
if guide_form.is_valid() and deck_form.is_valid():
new_deck = deck_form.save(commit=False)
new_deck.dust = 0
new_deck.save()
new_guide = deck_form.save(commit=False)
new_guide.author = Account.objects.get(id=request.user.id)
new_guide.deck = Deck.objects.get(id=new_deck.id)
new_guide.save()
else:
print(guide_form.errors)
print(deck_form.errors)
else:
deck_form = DeckForm(prefix = 'DeckForm')
guide_form = GuideForm(prefix = 'GuideForm')
return render(request, 'hsguides/guide_create.html', {
'DeckForm': DeckForm,
'GuideForm': GuideForm,
})
I use commit=False so I still can insert the deck in my guide. Now my problem comes with creating the template. When I submit the forms with my current set up, I receive no errors any more but the guide and the deck aren't saved either!
<div style="width: 60%; margin: 0 auto;">
<form class="form-horizontal" method="POST" action="{% url 'GuideCreate' %}"> {% csrf_token %}
<fieldset>
{% for field in DeckForm %}
{% if field.errors %}
<div class="class-group error">
<label class="control-lable">{{ field.label }}</label>
<div class="controls">{{ field }}
<span class="help-inline">
{% for error in field.errors %}{{ error }}{% endfor %}
</span>
</div>
</div>
{% else %}
<div class="control-group">
<label class="control-label">{{ field.label }}</label>
<div class="controls">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
</fieldset>
<fieldset>
{% for field in GuideForm %}
{% if field.errors %}
<div class="class-group error">
<label class="control-lable">{{ field.label }}</label>
<div class="controls">{{ field }}
<span class="help-inline">
{% for error in field.errors %}{{ error }}{% endfor %}
</span>
</div>
</div>
{% else %}
<div class="control-group">
<label class="control-label">{{ field.label }}</label>
<div class="controls">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
</fieldset>
<div class="form-actions" style="margin-top: 4px;">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>
</div>
Edit I don't receive an error message any more, the deck gets created as well but the guide that belongs to it not.
Just Change
return render_to_response('hsguides/guide_create.html', {
'DeckForm': DeckForm,
'GuideForm': GuideForm,
})
to
return render(request,'hsguides/guide_create.html', {
'DeckForm': DeckForm,
'GuideForm': GuideForm,
})
You're saying the guide doesn't get created. I see this line new_guide = deck_form.save(commit=False)..... shouldn't it be new_guide = guide_form.save(commit=False) ?
Not 100% but this might be a mistake. Hopefully, it's not just because you refactored the code for putting it here.
I didnot check the whole thing, but this place is wrong, it should be:
if request.method == "POST":
deck = DeckForm(request.POST)
if deck.is_valid():
# ....
you are not packing the POST data into your forms..
the same goes for the second form as well
Try this, you might forgot to pass the RequestContext:
from django.template import RequestContext
return render_to_response('hsguides/guide_create.html',
{'DeckForm': DeckForm, 'GuideForm': GuideForm},
RequestContext(request))
It passes the csrf token to the template.