When a post is created, the user's User ID is saved to the post table under the user_id field. On the home page (for right now) I'm outputting all the posts in the post table. I want to get the username into the template based off of the user ID on the Post object.
models.py
class Post(models.Model):
user_id = models.IntegerField()
post_name = models.CharField(max_length=300)
total_votes = models.IntegerField(default=0)
created_date = models.DateTimeField()
views.py
#login_required
def home(request):
posts = Post.objects.raw('SELECT * FROM posts ORDER BY created_date DESC')
return render(request, 'home.html', {'posts': posts})
home.html
{% for post in posts %}
<div>
{{ post.post_name }}
<p>{{ post.created_date }}</p>
<p>{{ post.total_votes }}</p>
<p>{{ post.user_id }}</p>
</div>
{% endfor %}
Edit: Ended up changing query to:
SELECT
*, u.username
FROM posts p
LEFT JOIN auth_user u ON (p.user_id = u.id)
I didn't think you could add to the Post object and even call it as post.username. Regardless, if there's a better way to do it, let me know. Thanks!
Your whole logic is wrong :(
The user should be stored as a ForeignKey into the Post model. That is, a user may have multiple Posts but one Post may belong to one user. Something like that:
class Post(models.Model):
user = models.ForeignKey(User, related_name='posts')
post_name = models.CharField(max_length=300)
total_votes = models.IntegerField(default=0)
created_date = models.DateTimeField()
Note that the User model may be the built-in Django User model (from django.contrib.auth.models import User) or you own if you have customized it.
Database queries in Django, are not made with raw SQL. That's why the ORM exists. So, the view could be changed to:
#login_required
def home(request):
posts = Post.objects.order_by('-created_date')
return render(request, 'home.html', {'posts': posts})
Now you have a very verbose and pythonic access to the User model instance (and to the Post one) from your template.
{% for post in posts %}
<div>
{{ post.post_name }}
<p>{{ post.created_date }}</p>
<p>{{ post.total_votes }}</p>
<p>{{ post.user.username }}</p>
</div>
{% endfor %}
EXTRA TIP: Instead of href="posts/{{post.id}}", try to use the {% url %} template tag in order not to hardcode url paths like that.
It would be better to use ForeignKey for such purpose. For example:
class Post(models.Model):
user = models.ForeignKey(User)
post_name = models.CharField(max_length=300)
total_votes = models.IntegerField(default=0)
created_date = models.DateTimeField()
Make sure to import User in models.py.
Create a Form for creating Post:
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('post_name',)
Now, in views:
def create_post(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.save()
return # Write return here
else:
form = PostForm()
context = {
'form': form
}
return render(request, 'template_name', context)
Create a template for this and use it to create posts.
In your view home:
#login_required
def home(request):
posts = Post.objects.order_by('-created_date')
return render(request, 'home.html', {'posts': posts})
In your template:
{% for post in posts %}
<div>
{{ post.post_name }}
<p>{{ post.created_date }}</p>
<p>{{ post.total_votes }}</p>
<p>{{ post.user.username }}</p>
</div>
{% endfor %}
It will work.
Related
So I have been trying to build a manager of activities with django and I'm still on the scratch. I was trying to test if the code can simply show if a acitivity is done or not, but it don't change anything. Everything looks good to me and I don't know if the error is in the form, in the view that saves the form or in anything else. I already tried to change the widget and fields of form and haven't been succeed in it.
models.py:
from django.db import models
from django.contrib.auth.models import User
class Activity(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=30)
is_complete = models.BooleanField()
def __str__(self):
return self.title
forms.py:
from .models import Activity
class ActivityUpdateForm(ModelForm):
class Meta:
model = Activity
fields = ['is_complete']
views.py:
from .models import Activity
from .forms import ActivityUpdateForm
def home(request):
acts = Activity.objects.all()
if request.method == 'POST':
form = ActivityUpdateForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
return redirect('home')
else:
form = ActivityUpdateForm()
context = {
'acts': acts,
'form': form,
}
return render(request, 'diary/home.html', context)
template:
{% for act in acts %}
<form method="POST">
{% csrf_token %}
<p>{{ act.title }}</p>
<p>Is complete: {{ act.is_complete }}</p>
{{ form }}
<p>{{ act.author }}</p>
<button type="submit">Submit</button>
</form>
{% endfor %}```
This is not elegant, but you just might need to do this:
data = Activity.objects.get(author=request.user)
data.is_complete = form.cleaned_data["is_complete"]
data.save()
Please delete form.save() and write the code above.
i'm not sure if this works, but please give it a try.
Why my custom template tag doesn't work?
templatetags.py:
from django import template
from ..models import User
register = template.Library()
#register.inclusion_tag('main/post_detail.html', takes_context=True)
def get_user_liked_posts():
request = context['request']
user = User.objects.get(username=request.user.username)
liked_posts = []
for post in user.liked_posts.all():
liked_posts.append(post.name)
return {'liked_posts': liked_posts}
post_detail.html:
{% load static %}
{% load templatetags %}
<nav class="blog-pagination" aria-label="Pagination">
<span id="likes_count">{{ post.likes_count }}</span>
{% if post.name in liked_posts %}
<button id="like_button" class="btn btn-outline-primary btn-primary text-
white">Like</button>
{% else %}
<button id="like_button" class="btn btn-outline-primary">Like</button>
{% endif %}
</nav>
views.py:
class PostDetailView(DetailView):
model = Post
slug_field = 'url'
class LikePostView(View):
def post(self, request, slug):
post = Post.objects.get(id=request.POST['id'])
user = User.objects.get(username=request.user.username)
if request.POST['like'] == 'true':
post.likes_count += 1
user.liked_posts.add(post)
else:
post.likes_count -= 1
user.liked_posts.remove(post)
user.save()
post.save()
return redirect('post_detail', slug)
models.py:
class Post(models.Model):
"""
This is post model
"""
name = models.CharField(max_length=150, blank=False)
article = models.TextField(blank=False)
image = models.ImageField(upload_to='uploads/', blank=True)
likes_count = models.IntegerField(default=0)
url = models.CharField(max_length=150, blank=False)
def get_absolute_url(self):
return reverse('post_detail', kwargs={'slug': self.url})
I want to check if the post is in the liked post of the current user, but it doesn't work.
It doesn't show any errors, it just does nothing.
User in my app must like or unlike posts. In models, I have many to many relationship user with the post. I want to check if the user likes this post
The problem is that you don't even use the template tag, furthermore this is not even needed as you can simply write something like so in the template:
{% if post in request.user.liked_posts.all %}
A Liked post
{% else %}
Not a liked post
{% endif %}
But this is a bit inefficient as we are getting all the posts liked by the user just to check if they like some post. Also if this were in a loop with multiple posts we would be making a query for each post.
Instead we can simply annotate whether the user likes a post in the view itself using an Exists subquery [Django docs] on the through model of the many to many:
from django.db.models import Exists, OuterRef
class PostDetailView(DetailView):
model = Post
slug_field = 'url'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(
liked_by_user=Exists(
User.liked_posts.through.objects.filter(
post_id=OuterRef("pk"),
user_id=self.request.user.id
)
)
)
return queryset
Now in the template we can simply write:
{% if post.liked_by_user %}
A Liked post
{% else %}
Not a liked post
{% endif %}
Note: Your way of saving the count similarly can simply be turned into an annotation using the Count aggregation function [Django
docs].
Generally one should not store calculated attributes in a column since
that might lead to inconsistent data when updating and forgetting to update the related count, etc.
I want to show all posts of a specific user in their profile page but only their profile information is being displayed but not the posts.
My initial code was simply trying to loop through the posts in the template like this:
{% for post in user.posts.all %}
{% endfor %}
But it didn't work. So I tried this next.
views.py
user = get_object_or_404(User, username=username)
post = Post.objects.filter(created_by=user.id)
context = {
'user': user,
'post': post
}
return render(request, 'users/profile.html', context)
profile.html
<div class="user-profile">
<img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
{{ user.username }}
{{ user.email }}
</div>
<div class="user-post-model">
{% for post in posts %}
<img class="rounded-circle post-img" src="{{ post.created_by.profile.image.url }}">
{{ post.title }}
{{ post.content|safe }}
{{post.tag}}
{{ post.created_by }}
{{ post.created_at|naturaltime}}
{% endfor %}
</div>
It didn't work either
I also tried using a ListView to post the posts. It worked but instead of showing the specific user's profile information it showed the logged in user's profile information but the specific user's posts.
like this:
def profile(request, username):
user = get_object_or_404(User, username=username)
context = {
'user': user
}
return render(request, 'users/profile.html', context)
class UserPostListView(ListView):
model = Post
template_name = 'users/profile.html'
context_object_name = 'posts'
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(created_by=user).order_by('-created_at')
This is the post model
class Post(models.Model):
title = models.CharField(max_length=255)
content = RichTextField(blank=True, null=True, max_length=30000)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
tag = models.CharField(max_length=255, default='uncategorised')
class Meta:
ordering = ('-created_at',)
def __str__(self):
return self.title + ' | ' + str(self.created_by)
def get_absolute_url(self):
return reverse("post-detail", kwargs={"pk": self.pk})
What is wrong?
To loop over the related Post objects to User, use _set (Django Docs).
So in your case user.post_set:
{% for post in user.post_set.all %}
{{ post.title }}
{% endfor %}
I'm learning Django and I am currently building blog app. I have a problem. I built functionalities to add new posts and to comment to new posts. Now I struggle with creating the possibility to delete comments by users.
I assigned an id (primary key) of given comment to button "delete" which is present in all comments fields in purpose to know which comment I want to delete. But now I don't know how to access to this HTML elements in backend and fetch this id's.
Part of my html file:
{% for comment in comments %}
{% if comment.post == object %}
<div class="article-metadata">
<small>{{ comment.add_date|date:"M/d/Y" }} <b>{{ comment.author }}</b></small>
{% if comment.author == user %}
Delete
{% endif %}
<p>{{ comment.comm_content }}</p>
</div>
{% endif %}
{% endfor %}
My class based view in views.py where I want to touch this id:
class PostDetailView(DetailView):
model = Post
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = Comment.objects.all()[::-1]
return context
# In this function I want to access my comment id and remove this comment from database
def delete_comment(self):
post_to_delete = Post.objects.get(id=my_comment_id)
del post_to_delete
I know this can be solved somehow using jquery but I don't know javascript and for now I would like to know how to do it using python only. Thanks for any hints.
My models.py file:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse
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})
class Comment(models.Model):
comm_content = models.TextField()
add_date = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
def __str__(self):
return f"Comment of post {self.post} posted at {self.add_date}."
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.post.pk})
First, add a URL pattern like:
# urls.py
path('comments/delete/<int:pk>/', views.CommentDeleteView.as_view(), name='delete_comment'),
next, you need a view to handle the logic:
# views.py
from django.urls import reverse_lazy
class CommentDeleteView(DeleteView):
model = Comment
def get_success_url(self):
return reverse_lazy('post-detail', kwargs={'pk': self.object.post.pk})
# if you don't want to send POST request, you can use:
def get(self, request, *args, **kwargs):
return self.post(request, *args, **kwargs)
Django's generic DeleteView will delete the object only if you send a POST request to it, so we will call the post method in get, to make it work with a GET request; Although, generally speaking, it's not a good idea; you should add a confirmation form (like a modal) and send a POST from there.
and finally, for your template:
{% for comment in comments %}
{% if comment.post == object %}
<div class="article-metadata">
<small>{{ comment.add_date|date:"M/d/Y" }} <b>{{ comment.author }}</b></small>
{% if comment.author == user %}
Delete
{% endif %}
<p>{{ comment.comm_content }}</p>
</div>
{% endif %}
{% endfor %}
I've tried to import {{ user.userprofile. }} and {{ user.userprofile }} with no success. In the django shell I can access the user profile with UserProfile.objects.all().
{{ user. }} works fine, so I think it's an issue with my model, but I've checked the Django docs 'Models', 'database query', and 'model instance reference' and related S/O posts, but if it's my model that's the issue, I don't know what else to search for.
Thanks
models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class UserProfileManager(models.Manager):
pass
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user')
can_inst_nat = models.BooleanField(verbose_name='I can explain grammar and word usage to people in my NATIVE language.', default=False)
can_inst_tar = models.BooleanField(verbose_name='I can explain grammar and word usage to people in my TARGET language.', default=False)
wants_nat_lang_inst = models.BooleanField(verbose_name='I would like grammar and vocabulary explained to me in my NATIVE language.', default=False)
wants_tar_lang_inst = models.BooleanField(verbose_name='I would like grammar and vocabulary explained to me in my TARGET language.', default=False)
wants_cont = models.BooleanField(verbose_name='I would like to continue working with the same partner for several consecutive exchanges.', help_text='(If you want continuity, check this; If you like speaking to a variety of people, leave it blank.)', default=False)
image = models.ImageField(upload_to='profile_image', blank=True)
def __str__(self):
return self.user.username
def create_profile(sender, **kwargs):
user = kwargs['instance']
if kwargs['created']:
user_profile = UserProfile(user=user)
user_profile.save()
post_save.connect(create_profile, sender=User)
views.py
def edit_profile(request, pk):
user = User.objects.get(pk=pk)
user_form = EditProfileForm(instance=user)
ProfileInlineFormset = inlineformset_factory(User, UserProfile, fields=[
'image',
'can_inst_nat',
'can_inst_tar',
'wants_nat_lang_inst',
'wants_tar_lang_inst',
'wants_cont',
])
formset = ProfileInlineFormset(instance=user)
if request.user.is_authenticated and request.user.id == user.id:
if request.method == "POST":
user_form = EditProfileForm(request.POST, request.FILES, instance=user)
if user_form.is_valid():
created_user = user_form.save(commit=False)
formset = ProfileInlineFormset(request.POST, request.FILES, instance=created_user)
if formset.is_valid():
created_user.save()
formset.save()
return redirect(reverse('accounts:view_profile'))
return render(request, 'accounts/edit_profile.html', {
"noodle": pk,
"noodle_form": user_form,
"formset": formset,
})
else:
raise PermissionDenied
template
<h4>Preferences</h4>
<p> {{ user.userprofile }}</p>
<ul>
{% if user.profile.can_inst_nat %}
{{ user.userprofile.can_inst_nat }}
<li class="profile-list-item">"I <strong>can explain grammar and word usage</strong> to people <strong>in my <em>native</em> language</strong>."</li><br>
{% endif %}
{% if user.userprofile.can_inst_tar %}
<li class="profile-list-item">"I <strong>can explain grammar and word usage</strong> to people <strong>in my <em>target</em> language</strong>."</li><br>
{% endif %}
{% if user.userprofile.wants_nat_lang_inst %}
<li class="profile-list-item">"I <strong>want grammar and vocab</strong> explained to me <strong>in my <em>native</em> language</strong>."</li><br>
{% endif %}
{% if user.userprofile.wants_tar_lang_inst %}
<li class="profile-list-item">"I <strong>want grammar and vocab</strong> explained to me <strong>in my <em>target</em> language</strong>."</li><br>
{% endif %}
{% if user.userprofile.wants_cont %}
<li class="profile-list-item">"I <strong>want</strong> to continue working with <strong>the same partner</strong> for several <strong>consecutive exchanges</strong>."</li><br>
{% endif %}
</ul>
You have set related_name='user' on the foreign key on you UserProfile model, which means you have to access it with user.user.
related_name defines how you access the reverse relationship, not the forward one, so setting it to user is a bit confusing, and probably isn't how you intended for it to work.
You might consider changing the related_name to "profile" so that you can instead access it as user.profile.
Change the related_name from user to userprofile
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='userprofile')
and to not forget to passe the user into context.