Hello friends I am trying to figure out how to work properly with a One to Many relationship.
I want to create two models,
The first model is Post
And a second model is Comment
Now I say that every post has many comments, and every comment has one post.
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
message = models.TextField(max_length=256,null=True)
def __str__(self):
return str(self.title)
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
message = models.TextField(max_length=256, null=True)
I'm trying to figure out how I can get all the posts and all the comments so that all the comments match the post to which they belong
That is, in the functions in the views file how do I send back this option.
because on the HTML page I want to display the post with all its comments
but there are a number of posts, so how do I do that?
The only thought I can do is this:
dict = {}
all_posts = Post.objects.all()
for post in all_posts:
dict[post] = Comment.objects.filter(post=post).values()
print(dict)
but I have a feeling there is something better
You can access all related comments for a post by using post.comment_set.all() see Following relationships “backward”
{% for post in all_posts %}
{{ post }}
{% for comment in post.comment_set.all %}
{{ comment }}
{% endfor %}
{% endfor %}
To reduce the number of queries use prefetch_related to get all related comments for all posts in a single query
def posts_list(request):
all_posts = Post.objects.prefetch_related('comment_set')
return render(request, 'template.html', {'all_posts': all_posts})
Related
I have a simple ManyToOne relationship where users can post comments to a poller object.
Comments Model
class Comments(models.Model):
poller = models.ForeignKey(Pollers, on_delete=models.CASCADE, related_name='comments')
created_on = models.DateTimeField(auto_now_add=True)
comment = models.CharField(max_length=250)
created_by = models.CharField(max_length=80)
class Meta:
ordering = ['created_on']
def __str__(self):
return 'Comment {} by {}'.format(self.body, self.name)
The View
def single_poller(request, poller_id):
"""retrieves the item clicked by the user from DB and renders
the single_poller template for further user interaction
"""
# Retrieve the item via get
poller = Pollers.objects.get(poller_id=poller_id)
# Increase the view field of the object by
poller.poller_views += 1
# Save the changed instance and overwrite new view integer
poller.save()
# Get the form for comments
form = CommentsForm
context = {
'poller': poller,
'form': form
}
return render(request, 'pollboard/single_poller.html', context)
Now I want to render the comments for each poller into my template like so
<div id="comments-choice-one-wrapper" class="comments-wrapper">
{{ poller.comments.comment }}
</div>
Somehow it doesn't render the comment I created beforehand. I checked in Django admin if the comment is rly related to the poller and this looks just fine.
poller.comments.all is a QuerySet of Comments, so this is a collection, therefore it makes no sense to use .comment since that is an attribute of a Comment object, not an attribute of QuerySet with comments.
You can enumerate over the comments.all, and thus render these comments individually:
<div id="comments-choice-one-wrapper" class="comments-wrapper">
{% for comment in poller.comments.all %}
{{ comment.comment }}
{% endfor %}
</div>
In you Comments model, using self.name or self.body makes not much sense, since that model has no name property or field. You likely should use the __str__ on the Poller:
class Comments(models.Model):
# ⋮
def __str__(self):
return f'Comment {self.comment} by {self.poller}'
Note: normally a Django model is given a singular name, so Comment instead of Comments.
I try to build a blog and this blog in the home view www.site.com consist of posts and these posts have comments, Now I Show the posts using List [] because the user has the ability to follow the content and in this list, I show the content based on the user, Now I successfully to show the posts but this post contains comments that's mean I need to get the pk of the post but as I said this post in the home view www.site.com without any extra URL that's mean as My knowledge I can't pass the pk in the def home_screen_view(request, pk) because this raise error home_screen_view() missing 1 required keyword-only argument: 'pk'
So my qustion how can I get the pk in the base url www.site.com
My view
def home_screen_view(request, *args, **kwargs):
users = [user for user in profile.following.all()]
post = []
for u in users:
p = Account.objects.get(username=u)
posts = p.post_set.all()
post.append(posts)
my_posts = request.user.post_set.all()
post.append(my_posts)
if len(post):
post= sorted(chain(*post), reverse=True, key=lambda post: post.created_date)
posts = Post.objects.filter(pk=post.pk) # here I want to get the pk of the post in order to show the comments related this post
comment = PostCommentIDE.objects.filter(post=posts)
The url
path('', home_screen_view, name='home'),
My Post Model
class Post(models.Model):
author = models.ForeignKey(Account, on_delete=models.CASCADE)
article = models.TextField(null=True, blank=True)
photo_article = models.ImageField(max_length=255, upload_to=get_poster_filepath)
created_date = models.DateTimeField(auto_now_add=True)
My Comment Model
class PostCommentIDE(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='ide_com')
author = models.ForeignKey(Account, on_delete=models.CASCADE)
content = models.TextField()
created_date = models.DateTimeField(auto_now_add=True)
The post template
{% for post in posts %}
...
#here I want to render the comments that related to spesific post
{% for comment in comments %}
{{ comments.content }}
{% endfor %}
...
{% endfor %}
I use function based view
From your home_screen_view you can remove
comment = PostCommentIDE.objects.filter(post=posts)
Instead, in your template you can do:
{% for comment in post.ide_com.all %}
{{ comments.content }}
{% endfor %}
Explanation:
Your comment model PostCommentIDE has a ForeignKey relationship with Post. This enables you to get the related comments for a post. By default you could have accessed the comments with post.postcommentide_set.all, but as you've defined a related_name attribute on that relationship, it becomes post.ide_com.all. Read more about it here: https://docs.djangoproject.com/en/3.2/ref/templates/language/#accessing-method-calls
so I've just started studying Django, and ran into a problem.
I'm trying to create a user-specific page, in which if user logs in and inputs his/her info, the info is displayed on the screen, dynamically of course.
So let me show you the codes I wrote.
Here's models.py
class UserInfo(models.Model):
authuser = models.ForeignKey(User, on_delete=models.CASCADE, related_name = 'userinfo', null=True,
default=None)
name = models.CharField(max_length=50)
introduction = models.CharField(max_length=100)
And here's views.py
#login_required(login_url="/register")
def main(response):
thisUser = # I have no idea on which code to write here.
return render(response, 'main.html', {'thisUser' : thisUser})
And here's the html file, main.html
{% extends 'base.html' %}
{% block content %}
{{thisUser.name}}
{{thisUser.introduction}}
{% endblock %}
So this is what I've done so far. I have completed all the registration/login/logouts, as well as the forms for letting users input their info(name, introduction). And the next step I'm trying to take is this user specific page, but I have no idea on how to create it.
I would very much appreciate your help. Thanks. :)
First You user OneToOneField in Your UserInfo model as i give
class UserInfo(models.Model):
authuser = models.OneToOneField(User, on_delete=models.CASCADE, related_name = 'userinfo', null=True,
default=None)
name = models.CharField(max_length=50)
introduction = models.CharField(max_length=100)
Then makemigrations and then migrate
I think you done login/singup with user model
after login what ever page you render that write only the give line
#in you html file
{% extends 'base.html' %}
{% block content %}
Name : {{ request.user.userinfo.name }}
Introduction : {{ request.user.userinfo.introduction }}
{% endblock %}
If you face problem with extends user of onetoone field i give link refer it
User extends for profile or info
Sing up with profile
Login of user
if still you have problem let me know..!
better to change user field to onetoone field
thisUser = UserInfo.objects.get(authuser=request.user)
(also change def main(response) to def main(request)/same in render also)
request.user will give you current login user object
you can do same in template example:
<h1>Name: {{request.user.name}}</h1>
I'm struggling getting the right query for my project. Here is an example or my model :
from django.db import models
class Pictures(models.Model):
name = models.CharField(max_length=100)
bild = models.FileField(upload_to='article_pictures/')
articel = models.ForeignKey('articles', on_delete=models.CASCADE)
def __str__(self):
return self.name
class Articles(models.Model):
name = models.CharField(max_length=100)
text = models.TextField(max_length=2000)
published = models.BooleanField(default=False)
def __str__(self):
return self.name
how do I get the published artikles from the artikles class including the pictures (if there is one, or more)?
Thank you for your help
I don't think there is any exact query for this, but you can use prefetch_related to pre-load data from database. For example:
articles = Artikles.objects.filter(published=True).prefetch_related('pictures_set')
for article in articles:
article.pictures_set.all() # will not hit database
All published articles:
Articles.objects.filter(published=True)
A single published Article(Example):
article = Articles.objects.filter(published=True).first()
# and it's pictures
for picture in article.pictures_set.all():
print(picture)
Note: models have singular names, so you should rename Articles to Article and Pictures to Picture.
The related Pictures of an article article can be obtained with:
my_article.picture_set.all()
this is a queryset that contains all the related pictures.
We can obtain the Articles that are publised, and then fetch the related Pictures in two extra queries with:
articles = Article.objects.filter(published=True).prefetch_related('picture_set')
So in a template you can then render it like:
{% for article in articles %}
{{ article.name }}
{% for picture in article.picture_set.all %}
{{ picture.name }}
{% endfor %}
{% endfor %}
I'm building a news app that allows members to post comments on articles. I want to display the articles in my template and also display the number of comments that have been made on each article. I tried using the count method but, it retrieved the total number of comments in my comments table instead of the number of comments that a particular article has.
#models.py
class Article(models.Model):
#auto-generate indices for our options
ENTRY_STATUS = enumerate(('no', 'yes'))
#this will be a foreign key once account app is built
author = models.CharField(default=1, max_length=1)
category = models.ForeignKey(Category)
title = models.CharField(max_length=50)
entry = models.TextField()
dateposted = models.DateTimeField(default=timezone.now, auto_now_add=True)
draft = models.IntegerField(choices=ENTRY_STATUS, default=0)
lastupdated = models.DateTimeField(default=timezone.now, auto_now=True)
#prevents the generic labeling of our class as 'Classname object'
def __unicode__(self):
return self.title
class Comment(models.Model):
#this will be a foreign key once account app is built
author = models.CharField(default=1, max_length=1)
article = models.ForeignKey(Article)
dateposted = models.DateTimeField(auto_now_add=True)
comment = models.TextField()
def __unicode__(self):
#returns the dateposted as a unicode string
return unicode(self.dateposted)
#templates/list_articles.html
{% for comment in comments %}
{% if comment.article_id == article.id %}
{% if comments.count < 2 %}
#this is returning all comments in comment table
<b>{{ comments.count }} comment</b>
{% else %}
<b>{{ comments.count }} comments</b>
{% endif %}
{% endif %}
{% endfor %}
All the examples I've seen so far manually provide a value to filter by(e.g. Comment.objects.filter(article_id=x).count() ) In my case I only have access via the template.
#views.py
class ArticlesListView(ListView):
context_object_name = 'articles'
# only display published pieces (limit 5)
queryset = Article.objects.select_related().order_by('-dateposted').filter(draft=0)[:5]
template_name = 'news/list_articles.html'
# overide this to pass additional context to templates
def get_context_data(self, **kwargs):
context = super(ArticlesListView, self).get_context_data(**kwargs)
#get all comments
context['comments'] = Comment.objects.order_by('-dateposted')
#get all article photos
context['photos'] = Photo.objects.all()
#context['total_comments'] = Comment.objects.filter(article_id=Article)
return context
My intended result is to have a listing of all articles and a roll-up of comments made on that article below each article(e.g. Article 1: 4 comments, Article 5: 1 comment, etc...) Right now I'm getting: Article 1: 4 comments, Article 5: 4 comments(even though Article 5 only has 1 comment)
Any help is appreciated. I've spent 5 hours reading through the documentation but every example manually provides a value to filter by.
I'm not sure why you find this unexpected. comments is all the comments, so of course comments.count is a count of all the comments. How could it be otherwise? You don't filter them anywhere.
This is however a really really horrible way to do things. There is absolutely no reason to pass all comments to the template and then iterate through them to check if they're the right article. You have a foreign key from Comment to Article, so you should use the reverse relationship to get the relevant commments.
Leave out the Comment query altogether from your view, and in your template just do this (replacing that whole block of nested fors and ifs):
{{ article.comment_set.count }}
This however does one count query per article. A better solution is to use annotations, so you can do it all in one single query. Change your queryset to add the annotated count of related comments:
from django.db.models import Count
class ArticlesListView(ListView):
queryset = Article.objects.select_related().annotate(comment_count=Count('comments')).order_by('-dateposted').filter(draft=0)
and now you can just do
{{ article.comment_count }}