How to use two models from different apps in one template - python

I want two models from different apps to be rendered in one template. For example in my home page there are list of posts showing post author and post content accordingly. When I will click on author tag I want to show author profile at the top and list of posts by that author bellow. I am using class based view (ListView andd DetailView).
In my 'users app' I have Profile model and in 'blog app' I have Post model.
blog/models.py
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})
users/models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
phone = models.CharField(max_length=10, blank=True)
def __str__(self):
return f'{self.user.username} Profile'
users/views.py
class ProfileDetailView(DetailView):
model = Profile
# to get two model in one page
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['posts'] = Post.objects.all()
return context
my template
<div class="">
<legend class="border-bottom mb-4">Profile Info</legend>
<div class="media">
<img class="rounded-circle account-img" src="{{ object.image.url }}">
<div class="media-body">
<h2 class="account-heading">{{object.user.username}}</h2>
<p class="text-secondary">{{object.user.email}}</p>
<p class="text-secondary">{{object.user.profile.phone}}</p>
</div>
</div>
</div>
<div class="">
<legend class="border-bottom mb-4">User's Posts</legend>
<h2 class="account-heading">{{?????}}</h2>
<p class="text-secondary">{{??????}}</p>
</div>
urls.py
path('profile/<int:pk>/', views.ProfileDetailView.as_view(),
name='profile-detail'),
So, my question is how to make queries to get posts from selected/clicked user. User Profile part is working fine. But cannot get data from post model.

You need to extend your User model and add a get_absolute_url() method, which will return a relative or an absolute path to your author foreign key while rendering post.
See the official django docs: https://docs.djangoproject.com/en/2.2/ref/models/instances/#django.db.models.Model.get_absolute_url
{% for each in posts %}
<li>{{each.title}} | Written by {{each.author}}</li>
{% endfor %}

Since v1.7 (see release notes) django.contrib.auth.models.AbstractUser no longer defines a get_absolute_url() method.
As a consequence you need to create the get_absolute_url method for your User model so that you can use {{ user.get_aboslute_url}} in your templates.
This can be done either through a custom User model as dotslash227 suggest in his answer, or by adding the ABSOLUTE_URL_OVERRIDES to your settings, which might be somewhat simpler.
Ether way you then can {{ user.get_aboslute_url }} in your template (more like {{each.author.get_absolute_url}} in your case).
For the latter case, the settings part might look something like:
ABSOLUTE_URL_OVERRIDES = {
'auth.user': lambda u: "/users/%s/" % u.username,
}
Obviously, in any case, you still need to define a view for this pattern.

You can access the current user in your DetailView class using self.request.user
Hence to get a specific user's posts, this is what you might looking for -
context['posts'] = Posts.objects.filter(author=self.request.user) since author is a foreign key pointing to the Users model.
instead of
context['posts'] = Post.objects.all()
This should give you the posts of the current user. Let me know if you need clarification with anything.

Finally I got my answer. Thanks to all who tried to help me.
Problem was in querying. Comment from rohit keshav was a hint to my problem. Following is the code:
users/views.py
class ProfileDetailView(DetailView):
model = Profile
# to get two model in one page
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['posts']=Post.objects.filter(author=self.object.user)
return context

Related

How do I access objects (user info) across Django models? (Foreign Key, Beginner)

I have a Profile model and a Post model in my Django project:
class Profile(models.Model):
""" User profile data """
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='profile')
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
id_user = models.IntegerField()
bio = models.TextField(max_length=280, blank=True)
def __str__(self):
return str(self.user)
class Post(models.Model):
""" Model for user-created posts """
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='post')
post_text = models.TextField()
created_at = models.DateTimeField(default=datetime.now)
def __str__(self):
return self.user
I want to be able to display a user's first_name and last_name on their posts (which are shown on index.html with other all user posts), along with their username.
{% for post in posts %}
<div class="row d-flex justify-content-between">
<h5 class="col-12 col-md-6">{{ first_name here }} {{ last_name here }}</h5>
<h5 class="col-12 col-md-6">(#{{ post.user }})</h5>
</div>
I can display the username just fine (post.user above) because it's in the Post model. The first_name and last_name are in the Profile model, however, and I just can't figure out a way to access them.
View:
#login_required(login_url='signin')
def index(request):
""" View to render index """
user_object = User.objects.get(username=request.user.username)
user_profile = Profile.objects.get(user=user_object)
posts = Post.objects.all()
context = {'user_profile': user_profile, 'posts': posts, }
return render(request, 'index.html', context)
As well as getting all Post objects, my view contains a reference to the currently logged in user (user_object) and their profile (user_profile) for customising things in the navbar and links to the logged in user's profile.
Based on this, my logic is that I should be doing adding something like this to my view:
post_author = Post.user
author_profile = Profile.objects.get(user=post_author)
so that I can reference something like this in the {% for post in posts %} loop:
{{ post_author.profile.first_name }} {{ author.profile.last_name }}
But of course, that doesn't work:
TypeError at /
Field 'id' expected a number but got <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x7f176924f160>.
Request Method: GET
Request URL: http://localhost:8000/
Django Version: 3.2.13
Exception Type: TypeError
Exception Value:
Field 'id' expected a number but got <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x7f176924f160>.
I was following a tutorial but it doesn't cover this, and I'd really like to figure out where I'm going wrong and gain a better understanding of things. If I could get this right in principle, I should also be able to add the avatar to each post as well. On the other hand, maybe it's just not possible to do this the way I've set my project up?
You are on the right track!
The 'key' here is how things follow along relationships like foreignkey(many to one).
Currently you're in your template, cycling through your posts. You have the info you need, it's just a matter of accessing it. In a template, you can follow along a foreign key just with the dot. So we want to go from post -> fk -> user -> reverse fk -> profile -> field
(we'll worry about the reverse fk later, for now just know we can use the related name to traverse it)
So this should work:
{% for post in posts %}
<div class="row d-flex justify-content-between">
<h5 class="col-12 col-md-6">{{ post.user.profile.0.first_name }} {{ post.user.profile.0.first_name }}</h5>
<h5 class="col-12 col-md-6">(#{{ post.user }})</h5>
</div>
It's not ideal though...
We've used profile.0 because, with your current definitions profile->user is a foreignkey, or many-to-one relationship, so you are allowing multiple profiles for the same user. Thus post.user.profiles will return a set (even if it's a set of one) and we only want the first object in the set, hence .0. If you only want one profile per user (and generally you do), change your
class Profile(models.Model):
""" User profile data """
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='profile')
to
user = models.OneToOneField(
User, on_delete=models.CASCADE, related_name='profile')
then remigrate your database. Now you can use
<h5 class="col-12 col-md-6">{{ post.user.profile.first_name }} {{ post.user.profile.first_name }}</h5>
The next problem is you will make a database call EVERY TIME you get poster info as you cycle through it, which will be slow if you've got lots of posts. This is possibly a little further than you've gotten yet, but it's good to bear in mind for the future:
You can make your post cycle more efficient by preloading all the required info in a single DB call in your view.
If you've done as I suggested and made the profile a one-to-one relationship, then we can also use select_related to follow the poster to their profile and grab the profile info when we get the post (there's a foreignkey from post to user and a one_to_one from user to profile, so select_related should work, otherwise we need prefetch_related to follow a foreignkey)
Post.objects.all().select_related('user__profile')
You access it in exactly the same way in your template - it's just grabbed more efficiently.

Django Forum App, comments don't update on user-side, but can be seen through admin

For reference, here are my models in my Forum app:
class Forum(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('forum-detail', kwargs={'pk': self.pk})
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
forum = models.ForeignKey(Forum, on_delete=models.CASCADE)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
To display the forum posts, I have a CBV 'ForumListView':
class ForumListView(ListView):
model = Forum
template_name = 'forum/forum.html'
context_object_name = 'forum_posts'
ordering = ['-created_at']
From this list, the user can click on any forum and it will lead them to 'forum-detail' with the CBV 'ForumDetailView':
class ForumDetailView(DetailView):
model = Forum
extra_context = {
'comments': Comment.objects.all().order_by('-created_at')}
Here is where I passed in the comments from my Comment model to be shown alongside the post. I think this is the reason why the comments don't update, but I'm not too sure how to fix this.
In the template for forum_detail.html, this is how I display all the comments made:
{% for comment in comments %}
{% if comment.forum == forum %}
<div class="content-section">
<p>{{ comment.description }}</p>
<small>{{ comment.user.username }}, on {{ comment.created_at|date:"F d, Y" }}</small>
</div>
{% endif %}
{% endfor %}
Note that the new comment made will be shown if I re-run
python manage.py runserver
and sometimes the new comment appears after a few minutes of waiting/refreshing the page.
Also, I think function-based views may fix this, however I have coded my entire app with CBVs and would love a fix that doesn't involve re-coding everything!
Any help is greatly appreciated, and can provide more info/code if needed!
Putting it in extra_context like this will cause the queryset to be evaluated when you define the view, and any values that it has at that time will be the only values that the view will get. That's why it is working when you restart the server. So it should be dynamic and fetched every time a new request comes. In that case you need to put it in get_context_data
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = Comment.objects.all().order_by('-created_at')}
return context

How to Retrieve Post by Each Category in a Blog and Place them on postlist html page in django

Am creating a blog and a news website in django which have a specific category displayed on its hompage.
That is the specific category for news, Politics and education are beeing displayed on my post_list.html.
I get a no exception provided when i try to retrieve it by
Post.objects.filter(category__name__in=["news"])
This is the model for my category
class Category(models.Model):
name = models.CharField(max_length=250)
slug = models.SlugField(max_length=250, unique=True)
This is the model for my Post
class Post(models.Model):
image = models.ImageField(upload_to="images/")
title = models.CharField(max_length=150)
summary = models.CharField(max_length=250)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
*This is the view for my postlist
class PostListView(ListView):
model = Post
template_name = blog/post_list.html
context_object_name = "posts"
ordering = ["date_posted"
paginate_by = 5
def get_queryset(self):
posts =
get_object_or_404(Post)
return Post.objects.filter().order_by( "-date_posted")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['posts'] = Post.objects.filter()[0:4]
context['new_cat'] =Post.objects.filter(category__name__in=[" news"])[0.5]
context['new_politics'] = Post.objects.filter(category__name__in=["politics"])[0.5]
return context ***
This is my pos_tlist.html code for retrieving post by category in news
<div> <h1>News</h1> <div/>
{% for post in new_cat%}
div class="mt-2"
<img class="object-cover w-full rounded-lg" src="{{ post.image.url }}" alt="{{ post.title }} cover"> </div>
<div class="py-2 flex flex-row items-center justify-between">
<h1 class="hover:text-blue-600 text-gray-900 font-bold text-xl mb-2 py-2">{{ post.title }}</h1></div> '''
I get a no exception provided for this post when i run python manage.py runserver
But when i go to my shell , python manage.py shell
I get a full list of all the post.
But it refuses to display on hompage
I need help in doing this and making all my category appear per category on news page
Is there any solution for all my post to appear category by category on post_list.html and homepage
In summary- i can't filter post that have a Foreign key Models like Category, Author in my Post Model.
I can directly filter post from the post model but i can't filter post title from the category Name models
That means when i try to filter Post.objects.all() then all my post will come out but when i try Post.objects.filter(category__name__in=["news"]) nothing will show in my page except no exeption error
Help
Could a problem be in whitespace you have in " news" here?
Post.objects.filter(category__name__in=[" news"])[0.5]
Also, what is [0.5]? I guess you want [0:5] there?
Also, make sure your get_queryset and get_context_data methods are indented properly. Because if they have no indent as you showed it then they defined outside of your PostListView class...

How to return multiple items in a get_queryset function in Django?

So I have a web app where the user can enter their information, and eventually I want to display all of it, but at the moment this code right here
class UserPostListView(ListView):
model = Post
template_name = 'mainapp/user_posts.html'
context_object_name = 'posts'
def get_queryset(self):
user = get_object_or_404(User,username=self.kwargs.get('username'))
first_name = get_object_or_404(User,first_name=self.kwargs.get('first_name'))
return Post.objects.filter(author=user).order_by('-published_date')
It gives me an error, and it says User not found.
I have tried add this to the end of the return statement
.order_by('-published_date'),first_name
However this did not work.
This is the relevant urls.py file responsible for the user posts
path('user/<str:username>', UserPostListView.as_view(), name='user-posts'),
This is the UserProfileInfo model
class UserProfileInfo(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=50,blank=True,null=True)
last_name = models.CharField(max_length=50,blank=True,null=True)
description = models.CharField(max_length=150)
image = ProcessedImageField(upload_to='profile_pics',
processors=[ResizeToFill(150, 150)],
default='default.jpg',
format='JPEG',
options={'quality': 60})
joined_date = models.DateTimeField(blank=True,null=True,default=timezone.now)
verified = models.BooleanField( default=False)
def __str__(self):
return f'{self.user.username} Profile'
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
And here is the relevant bit of the user-posts.html
<div class="sidebar">
<p class="active" href="#">{{ view.kwargs.username }}</p>
<button class="commentbtn"><a class="aclass" href="#">Connect with {{ view.kwargs.username }}</a></button>
<p>{{ view.kwargs.first_name }}</p>
<p>Lorem</p>
</div>
I want to be able to display the first name of the person in the ```{{ view.kwargs.first_name }}, however everything I have tried has failed to work
I expected no errors to occur and for this to work, however this is not the case. Is it possible to have 2 get_queryset methods, or is there something I can do in the current one to achieve my goal of displaying the information of the user
Thanks for any help you can give :)
How about this?
def get_queryset(self):
qs = super().get_queryset() #this is the same as Post.objects.all()
user = self.request.user
return qs.objects.filter(author=user).order_by('-published_date')
Now you can access this query using object_list on your template, but since you changed it's name with context_object_name='posts', use posts instead:
{% for post in posts %}
<h1>{{ post.first_name }}</h1>
...
{% endfor %}
But it looks like the Post model isn't going to have a first_name column?
You might be better off using the request.user object here as well:
<h1>{{ request.user.first_name }}</h1>
<h2>{{ request.user.username }}</h2>

List all the compositions by a specific composer in their profile details page

I want a public page showing the profile of a specific user (so I cannot grab the user id from the logged-in users). I'm not able to select their specific compositions.
I am using a custom user model, so that I have a User class and then a Profile class which is linked to the User via OneToOneField (see code below). I also have a Composition class, which is linked to a specific composer via a ForeignKey. I am able to get the details of a specific profile and I'm also able to print out all the compositions (using Composition.objects.all()).
My models.py:
class User(AbstractBaseUser):
email = models.EmailField(unique=True, max_length=255)
full_name = models.CharField(max_length=255, blank=True, null=True)
objects = UserManager()
[...]
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
[...]
def get_absolute_url(self):
return reverse('profile', args=[str(self.id)])
class Composition(models.Model):
title = models.CharField(max_length=120) # max_length = required
description = models.TextField(blank=True, null=True)
composer = models.ForeignKey(
USER, #my custom user model
on_delete=models.CASCADE
)
def get_absolute_url(self):
return reverse("composition-detail", kwargs={"id": self.id})
def __str__(self):
return "%s" % (self.title)
My views.py:
def profile_details(request, id):
compositions = Composition.objects.filter(composer__id=id)
context = {
"object_list": compositions
}
return render(request, "profile.html", context)
My urls.py:
path('profile/<int:id>/', views.profile_details, name='profile')
My template.html:
{% extends "base.html" %}
{% block content %}
<div class="content-section">
<div class="media">
<img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
<div class="media-body">
<h2 class="account-heading">{{ user.full_name }}</h2>
<p>{{ obj.profile_id}}</p>
<p class="text-secondary">{{ user }}</p>
{% for composition in object_list %}
<li>{{ composition.title }}</li>
{% endfor %}
</div>
</div>
</div>
{% endblock content %}
I'm expecting to see the compositions by that specific composer, i.e. user associated to the profile I'm viewing.
Edit
I've made some progress by adding some custom data to my view:
class ProfileDetails(generic.DetailView):
model = Profile
template_name = 'profile.html'
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['compositions'] = Composition.objects.filter(id=1)
return context
Now the context contains indeed all the composition. How do I filter only the composition by the user connected to the profile I'm viewing?
After many tests I've apparently finally come to a solution.
My views.py:
def profile_details(request, pk):
compositions = Composition.objects.filter(composer__id=pk)
context = {
"compositions": compositions,
}
return render(request, "profile.html", context)
And my urls.py:
path('profile/<int:pk>/', views.profile_details, name='profile')
The pk, which is the same for User and Profile objects, is taken from the URL, and used for filtering the Composition objects and find only the compositions by that composer.

Categories