How to access data of Django ManyToMany Field? - python

I want to Create a Like Functionality in Home page of my Social media Website. I am using ManyToManyField for Storing Likes on Particular post as shown in models.py. In my home page I have list of Posts and I want to check weather a Posts in already Liked by Current Logged in User or not.
In my views.py I am using
post = Posts.objects.filter('likes')
if post.likes.filter(id=request.user.id).exists():
models.py
class Posts(models.Model):
title = models.CharField(max_length=250, blank=False)
content = models.CharField(max_length=15000,
help_text="Write Your thought here...")
likes = models.ManyToManyField(User, blank=True)
views.py
def home(request):
post = Posts.objects.filter('likes')
print('Thats just Test', post)
if post.likes.filter(id=request.user.id).exists():
print("Already Exixts")
is_liked = False
context = {
'all_posts': all_posts,
'is_liked': is_liked,
}
return HttpResponse(template.render(context, request))
hometemplte.html: (Only Like Button)
<form action="{% url 'like_post' %}" method="POST">
{% csrf_token %}
{% if is_liked %}
<button type="submit" name="like" value="{{ post.id }}" class="btn upvote liked">Liked</button>
{% else %}
<button type="submit" name="like" value="{{ post.id }}" class="btn upvote">Upvote</button>
{% endif %}
</form>

If you want to get data of the ManyToMany field, for backwards mapping you need to use related_name parameter while declaring the model.
So your attribute will be:
likes = models.ManyToManyField(User, blank=True, related_name='likes')
Your query to check if the particular post has been liked by the user or not is:
post.likes.filter(id=request.user.id).exists():
Update
The issue in your case is you are retrieving multiple posts in a single line:
Posts.objects.filter('likes'), which returns a query set.
You need to fetch a particular post and then check if the user likes the post or not.
post = Posts.objects.all()[0] will not throw any error.

Related

Getting id of instance in form and save in it

I am building a Blog App and I am working on a feature in which A user can report comment so I created another model for storing reports so i am saving which comment is reported But I placed report form in detail view so report form will be below the comment in post detail page, In which I am not getting the comment id when reporting.
models.py
class Blog(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=1000)
class Comment(models.Model):
commented_by = models.ForeignKey(User, on_delete=models.CASCADE)
body = models.CharField(max_length=1000)
class ReportComment(models.Model):
reported_by = models.ForeignKey(User, on_delete=models.CASCADE)
reported_comment = models.ForeignKey(Comment, on_delete=models.CASCADE)
text = models.CharField(max_length=1000)
views.py
def blog_detail_page(request, blog_id):
post = get_object_or_404(Blog, pk=blog_id)
if request.method == 'POST':
reportform = CommentReportForm(data=request.POST)
if FlagCommentForm.is_valid():
form = reportform.save(commit=False)
# Saving in this line
flagForm.reported_comment = reportform.id
form.reported_by = request.user
form.save()
return redirect('home')
else:
reportform = CommentReportForm()
context = {'reportform':reportform, 'post':post}
return render(request, 'blog_detail_page.html', context)
blog_detail_page.html
{{post.title}}
{% for comment in post.comment_set.all %}
{{comment.body}}
<div class="container">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<table>
{{ reportform }}
</table>
<button type="submit">Save</button>
</form>
</div>
{% endfor %}
What have I tried :-
I have also tried by using loop like :-
comments = post.comment_set.all()
for com in comments:
if request.method == 'POST':
......
if reportform.is_valid():
....
......
......
form.reported_by = com
But it always saved the first comment id.
Then I tried by request.POST method, Like :-
comment_ID = request.POST['comment_id']
But is showed MultiValueDictKeyError error.
I have tried many times But the id of comment is not saving with report instance.
You will need to add the primary key of the comment to the form, or to the URL where you submit the form to. For example as a hidden form element:
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="hidden" name="comment_id" value="{{ comment.pk }}">
<table>
{{ reportform }}
</table>
<button type="submit">Save</button>
</form>
An alternative is to make a URL where you report a comment to with:
urlpatterns = [
path('comment/<int:comment_id>/report', some_view, name='report-comment')
]
then you can submit the form to that view with:
<form method="post" action="{% url 'report-comment' comment_id=comment.pk %}" enctype="multipart/form-data">
{% csrf_token %}
<table>
{{ reportform }}
</table>
<button type="submit">Save</button>
</form>

Exception Value: Field 'id' expected a number but got ''

(I searched this error on StackOverflow and tried the solution ideas, but hey did not work.)
In my Django Project, I tried to add a like button to my article detail page. If users like the article, they click the like button and return the same article page. I saw this video on Youtube and wrote the same code by changing some values according to my project. But I encountered an error which says Field 'id' expected a number but got ''.
I read some solutions on Stackoverflow and tried to delete migrations files (except init.py), and then I wrote python manage.py makemigrations and then python manage.py migrate. Unfortunately, it did not work. I want to share my code, so maybe you can give me some solution ideas.
model.py
class Article(models.Model):
author = models.ForeignKey("auth.User", on_delete = models.CASCADE, verbose_name="Kullanıcı")
title = models.CharField(max_length = 50, verbose_name="Başlık")
content = RichTextField()
created_date = models.DateTimeField(auto_now_add = True, verbose_name="Oluşturma Tarihi")
likes = models.ManyToManyField(User, related_name='blog_post')
urls.py
urlpatterns = [
path('dashboard/',views.dashboard, name ="dashboard"),
path('addarticle/',views.addarticle, name ="addarticle"),
path('article/<int:id>',views.detail, name ="detail"),
path('update/<int:id>',views.updateArticle, name ="update"),
path('delete/<int:id>',views.deleteArticle, name ="delete"),
path('',views.articles, name ="articles"),
path('comment/<int:id>',views.addComment, name ="comment"),
path('like/<int:id>',views.LikeView, name ="like_post"),
]
views.py
def LikeView(request, id):
post = get_object_or_404(Article, id=request.POST.get("post_id"))
article.likes.add(request.User)
return HttpResponseRedirect(reverse("detail", args=[str(id)]))
detail.html (In this html, I show the content of the article and who published it, comments and likes)
<form action="{% url 'article:like_post' article.id %}" method="POST">
{% csrf_token %}
<button type="submit" name="post_id" value="{{ article_id }}" class="btn btn-primary btn-sm">
<i class="bi bi-hand-thumbs-up"></i> Like
</button>
</form>
You can see likes table in this photo I can like a post in admin page. But obviously thats not appropriate for users.
And thats the error page I encountered.
The value is article.id, not article_id:
<button type="submit" name="post_id" value="{{ article.id }}" class="btn btn-primary btn-sm">
That being said, it does not make much sense to pass it both as URL parameter and as POST parameter. You can simply make a form without the post_id, and use the URL parameter instead:
from django.shortcuts import redirect
from django.views.decorators.http import require_POST
#require_POST
def LikeView(request, id):
post = get_object_or_404(Article, id=id)
post.likes.add(request.user)
return redirect('detail', id)
Then the form simply looks like:
<form action="{% url 'article:like_post' article.id %}" method="POST">
{% csrf_token %}
<button type="submit" class="btn btn-primary btn-sm">
<i class="bi bi-hand-thumbs-up"></i> Like
</button>
</form>
Note: One can use the #require_POST decorator [Django-doc]
to restrict the view to only be accessible for a POST request.

Django | Redirect View after "liking" without scrolling

I'm making a simple blog app. I have added the ability to "like" a post on your feed. However, the only way I can figure out closing a view is by returning some form of redirect. The problem is, if the post you're "liking" is halfway down the page, I don't want it to reset the zoom to the top of the page again. Is there a way simply to redirect to the same page without affecting zoom?
Here's my post model:
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="author")
likes = models.ManyToManyField(User, related_name="likes", blank=True)
def like(self, post):
self.likes.add(post)
def unlike(self, post):
self.likes.remove(post)
I have the following setup in Views:
#login_required
def like(request, pk):
post = Post.objects.get(id=pk)
Post.like(post, request.user)
return HttpResponseRedirect(reverse('Home'))
#login_required
def unlike(request, pk):
post = Post.objects.get(id=pk)
Post.unlike(post, request.user)
return HttpResponseRedirect(reverse('Home'))
Here's how I'm calling the Views from my URLs:
path('like/<int:pk>', views.like, name='Like'),
path('unlike/<int:pk>', views.unlike, name='Unlike'),
I'm using a form on my template to trigger the URL:
{% if user in post.likes.all %}
<form action="{% url 'Unlike' post.pk %}" method="POST">
{% csrf_token %}
<button type="submit" value="{{ post.id }}" class="unlike">UNLIKE</button>
</form>
{% else %}
<form action="{% url 'Like' post.pk %}" method="POST">
{% csrf_token %}
<button type="submit" value="{{ post.id }}" class="like">LIKE</button>
</form>
{% endif %}
Is there something I can change? I'm assuming there's something I'll need to change or add under "return" in my view functions?
One way to do this would be adding an anchor tag to each like button that uses the post ID, then when redirecting to the post from your view, include #pk at the end of the post URL to direct to that ID anchor down the page.

Django - update model with user input by id

I stuck with my first app in Django.
Basically it will be several objects on webpage, every object with input form. If user types and email address to input, this object should be marked as 'reserved'.
I tried several solutions (and problably because of this multi-solution none of them worked). At the beggining I used AJAX to update the status of the object and it worked, but when I implemented email address input it failed. So I found out that I should give up on AJAX and go after ModelForm.
So for now my not-working code is looking like this:
urls.py
urlpatterns = [
url(r'^$', views.index),
url(r'^occupy/$', views.occupy, name='occupy'),
index.html
{% for gift in gifts %}
<div class="panel-heading">
<h2 class="panel-title"> {{ gift.name }}</h2>
</div>
<div class="panel-body">
{{ gift.link }}
<form role="form" action="" method="POST">
{% csrf_token %}
{{ form }}
<input type="submit" value="szukaj" name='q' data-id="{{ gift.id }}">
</form>
</div>
{% endfor %}
models.py
class Gift(models.Model):
name = models.CharField(max_length=100)
link = models.URLField(max_length=100)
email = models.EmailField(blank=True, default='')
occupied = models.BooleanField(default=0)
forms.py
class ContactForm(forms.ModelForm):
class Meta:
model = Gift
fields = ('email',)
views.py
def index(request, id):
gifts = Gift.objects.all()
form_class = ContactForm()
if request.method == 'POST':
occupy(request)
def occupy(request):
print('hrj')
gift_id = request.GET.get('gift_id', None)
if (gift_id):
gift = Gift.objects.get(id=int(gift_id))
I guess that this parts are ok, but I am missing data flow (emails and ids).
Any help will be very appreciated.

How to make add replies to comments in Django?

I'm making my own blog with Django and I already made a Comments system.. I want to add the replies for each comment (like a normal comment's box) and I don't know what to do this is my current models.py comments:
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE)
text = models.TextField()
created_date = models.DateField(auto_now_add=True)
parent = models.ForeignKey('self', null=True, related_name='replies')
def __str__(self):
return self.text
and this is the .html where I use the comments
{% for comment in post.comments.all %}
<ul>
{{ comment.text }}
{% for reply in comment.replies.all %}
<li>
{{ reply.text }}
</li>
{% endfor %}
<ul>
{% endfor %}
and apparently It is working but when I try to make a comment in the admin site of Django it forces me to put a "Parent" to each comment (and this is not obligatory beacuse not every comment is a reply) I also don't know how to add the reply "button" in the HTML file. Please help tell me what changes can I do to make a simple comment box with replies . Thanks a lot
I had the same problem and resolved it as follows:
1.
For admin site as mentioned above just set blank=True for parent field. My comment model:
class Comment(models.Model):
post = models.ForeignKey(Post, related_name='comments')
name = models.CharField(max_length=80)
email = models.EmailField(max_length=200, blank=True)
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
# manually deactivate inappropriate comments from admin site
active = models.BooleanField(default=True)
parent = models.ForeignKey('self', null=True, blank=True, related_name='replies')
class Meta:
# sort comments in chronological order by default
ordering = ('created',)
def __str__(self):
return 'Comment by {}'.format(self.name)
remember to run makemigrations and migrate
2.Let's start with views. I'm using the post_detail
view to display the post and its comments. We add a QuerySet to retrieve all parent active comments for this post. After this, we validate the submitted data using the form's is_valid(). If the form is valid we check if submitted data comes from hidden input in replay button form. Next if parent_id exits we create parent object(parent_obj) for replay comment and replay_comment object, then we assign parent_obj to replay_comment.
If parent_obj is equal to None we just proceed with normal comment by creating new_comment object and saving it to the database.
def post_detail(request, post):
# get post object
post = get_object_or_404(Post, slug=post)
# list of active parent comments
comments = post.comments.filter(active=True, parent__isnull=True)
if request.method == 'POST':
# comment has been added
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
parent_obj = None
# get parent comment id from hidden input
try:
# id integer e.g. 15
parent_id = int(request.POST.get('parent_id'))
except:
parent_id = None
# if parent_id has been submitted get parent_obj id
if parent_id:
parent_obj = Comment.objects.get(id=parent_id)
# if parent object exist
if parent_obj:
# create replay comment object
replay_comment = comment_form.save(commit=False)
# assign parent_obj to replay comment
replay_comment.parent = parent_obj
# normal comment
# create comment object but do not save to database
new_comment = comment_form.save(commit=False)
# assign ship to the comment
new_comment.post = post
# save
new_comment.save()
return HttpResponseRedirect(post.get_absolute_url())
else:
comment_form = CommentForm()
return render(request,
'core/detail.html',
{'post': post,
'comments': comments,
'comment_form': comment_form})
Simple comment form:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('name', 'email', 'body')
* More about ModelForm
And lastly template. We need to create two forms. One form for comments and the second one for replays. Here you have simple template:
<!-- Comments Form -->
<h2>Add a new comment</h2>
<form action="." method="post">
{{ comment_form.as_p }}
{% csrf_token %}
<button type="submit">Add comment</button>
</form>
<!-- Comment with nested comments -->
{% for comment in comments %}
<div class="comment" style="background-color: powderblue">
<p class="info">{{ comment.name }} | {{ comment.created }}</p>
{{ comment.body|linebreaks }}
{% for replay in comment.replies.all %}
<p class="info">{{ replay.name }} | {{ replay.created }}</p>
<li>{{ replay.body }}</li>
{% endfor %}
<h5>Replay</h5>
<form action="." method="post">
{{ comment_form.as_p }}
{% csrf_token %}
<!-- Hidden input for parent comment.id -->
<input type="hidden" name="parent_id" value="{{ comment.id }}">
<input class="btn btn-primary" type="submit" value="Replay">
</form>
</div>
{% empty %}
<h4>There are no comments yet.</h4>
{% endfor %}
just add some nice css and maybe jquery to have fade in reply comments and that's all.
first Question:parent must be set in admin.
parent = models.ForeignKey('self', null=True, blank=True, related_name='replies')
blank=True can let you don't set parent in admin.
second Question:add comment dynamicly.
<form id="comment-form" method="post" role="form">
{% csrf_token %}
<textarea id="comment" name="comment" class="form-control" rows="4" placeholder="input comment!"></textarea>
<button type="submit" class="btn btn-raised btn-primary pull-right">submit</button>
</form>
$('#comment-form').submit(function(){
$.ajax({
type:"POST",
url:"{% url 'article_comments' article.en_title %}",
data:{"comment":$("#comment").val()},
beforeSend:function(xhr){
xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
},
success:function(data,textStatus){
$("#comment").val("");
$(".comment ul").prepend(data);
},
error:function(XMLHttpRequest, textStatus, errorThrown){
alert(XMLHttpRequest.responseText);
}
});
return false;
});
view.py:
print_comment = u"<p>comment:{}</p>".format(text)
if parent:
print_comment = u"<div class=\"comment-quote\">\
<p>\
<a>#{}</a>\
{}\
</p>\
</div>".format(
parent.user.username,
parent.text
) + print_comment
# current comment
html = u"<li>\
<div class=\"comment-tx\">\
<img src={} width=\"40\"></img>\
</div>\
<div class=\"comment-content\">\
<a><h1>{}</h1></a>\
{}\
<p>{}</p>\
</div>\
</li>".format(
img,
comment.user.username,
print_comment,
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
)
return HttpResponse(html)
models.py
class Comment(models.Model):
author = models.CharField(max_length=100)
comment_field = models.TextField()
date_created = models.DateTimeField(auto_now_add=True)
post = models.ForeignKey('Post', on_delete=models.CASCADE)
reply = models.ForeignKey('Comment', on_delete=models.CASCADE, related_name="replies", null=True)
def __str__(self):
return self.author
views.py
def post_detail(request, slug):
post = Post.objects.get(slug=slug)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
reply_obj = None
try:
reply_id = int(request.POST.get('reply_id'))
except:
reply_id = None
if reply_id:
reply_obj = Comment.objects.get(id=reply_id)
author = form.cleaned_data['author']
comment = form.cleaned_data['comment']
if reply_obj:
Comment(author=author,comment_field=comment, reply=reply_obj, post=post).save()
else:
Comment(author=author,comment_field=comment, post=post).save()
return redirect(reverse('post_detail', args=[post.slug]))
else:
form = CommentForm()
comments = Comment.objects.filter(post=post, reply=None).order_by('-date_created')
context = {
'post':post,
'form':form,
'comments':comments
}
return render(request, 'post_detail.html', context)
template (post_detail.html)
{% for comment in comments %}
{{comment.author}}
{{comment.date_created.date}}
{{comment.comment_field}}
{% for reply in comment.replies.all %}
{{reply.author}}
{{reply.date_created.date}}
{{reply.comment_field}}
{% endfor %}
<a class="text-decoration-none" data-bs-toggle="collapse" href="#collapseExample{{comment.id}}" role="button" aria-expanded="false" aria-controls="collapseExample">
Reply </a>
<div class="collapse" id="collapseExample{{comment.id}}">
<div>
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
{{ form.author }}
</div>
<div class="form-group mt-md-2">
{{form.comment}}
</div>
<input type="hidden" name="reply_id" value="{{ comment.id }}">
<button class="btn btn-primary mt-md-1" type="submit" value="Reply">Reply</button> </form>
</div>
</div>
<hr>
{% endfor %}
You need to create a field called parent which is a ForeignKey with the Comment model itself.
parent = models.ForeignKey('self' , null=True , blank=True , on_delete=models.CASCADE , related_name='replies')
Also, you can create a property called children in the Comment model which returns all the replies of the comment.
#property
def children(self):
return BlogComment.objects.filter(parent=self).reverse()
For detail explanation of comment and reply system in django you can read my article about that.

Categories

Resources