Django CBV queryset issue - python

this is my model
class Post(models.Model):
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
title = models.CharField(max_length=256)
text = models.TextField()
create_date = models.DateTimeField(auto_now_add=True, auto_now=False)
published_date = models.DateTimeField(blank=True, null=True)
def __str__(self):
return f'{self.author} --> {self.title}'
def get_absolute_url(self):
return reverse('blog:post_detail', kwargs={'pk': self.pk})
def publish(self):
self.published_date = timezone.now()
self.save()
def unpublish(self):
self.published_date = None
self.save()
and i registered it on Django admin. i have created two post on Django admin, the new post when i publish it and hit now and refresh the page, it won't show this post:
this is admin code:
from django.contrib import admin
from .models import Post, Comment
#admin.register(Post, Comment)
class PostModelAdmin(admin.ModelAdmin):
pass
this is the view:
class PostListView(ListView):
queryset = Post.objects.filter(published_date__lte=timezone.now())
print('query0: ', queryset)
def get_queryset(self):
queryset = super().get_queryset()
print('query1: ', queryset)
print('query2:', self.queryset)
print('query3: ', Post.objects.filter(published_date__lte=timezone.now()))
return Post.objects.filter(published_date__lte=timezone.now())
As you can see result of query1, query2 is different with query3.
I think this is a bug, if it's not please light me up.

it's been answered by
Iain Shelvington in a comment:
The class attribute queryset defined in PostListView is created once when your app starts, it will always filter using the value of timezone.now() at the time when your app started. To test this, try restarting your app after publishing and see if the result changes

Related

Creating a DetailView of a profile with a queryset of posts created by that user

I'm creating a twitter-like app and I'm stuck on creating a UserProfileView which is supposed to display a certain User's profile, along with a list of posts made by that user below. Though I can't really figure out a way to create a proper view for that.
I'm trying to use class based views for that, the one I'll be inheriting from is probably DetailView (for profile model) and something inside of that which retrieves a queryset of posts made by that user:
My profile model looks like this:
class Profile(models.Model):
user = models.OneToOneField(
User, on_delete=models.CASCADE, primary_key=True)
display_name = models.CharField(max_length=32)
profile_picture = models.ImageField(
default='assets/default.jpg', upload_to='profile_pictures')
slug = models.SlugField(max_length=150, default=user)
def get_absolute_url(self):
return reverse("profile", kwargs={"pk": self.pk})
Post model:
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateField(auto_now_add=True)
content = models.TextField(max_length=280)
image = models.FileField(upload_to='post_images/', blank=True, null=True)
def __str__(self) -> str:
return f'Post by {self.author} on {self.date_posted} - {self.content[0:21]}'
def get_absolute_url(self):
return reverse("post-detail", kwargs={"pk": self.pk})
I've tried creating this method:
class UserProfileView(DetailView):
model = Profile
context_object_name = 'profile'
template_name = 'users/profile.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_posts'] = Post.objects.filter(author=Profile.user)
return context
But this one sadly doesn't work, raising an error of
"TypeError: Field 'id' expected a number but got <django.db.models.fields.related_descriptors.ForwardOneToOneDescriptor object at 0x000001A5ACE80250>."
'ForwardOneToOneDescriptor' object has no attribute 'id' is returned if I replace the filter argument with author=Profile.user.id
I'm not sure whether it's a problem with the way I filtered Posts, or how I used get_context_data.
The object is stored as self.object, so you can filter with:
class UserProfileView(DetailView):
model = Profile
context_object_name = 'profile'
template_name = 'users/profile.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_posts'] = Post.objects.filter(author_id=self.object.user_id)
return context
An alternative might be to use a ListView for the Posts instead, to make use of Django's pagination:
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
class UserProfileView(ListView):
model = Post
context_object_name = 'posts'
template_name = 'users/profile.html'
paginate_by = 10
def get_queryset(self, *args, **kwargs):
return (
super()
.get_queryset(*args, **kwargs)
.filter(author__profile__slug=self.kwargs['slug'])
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['profile'] = get_object_or_404(Profile, slug=self.kwargs['slug'])
return context
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

Accessing UserProfile data from foreign key related Post

I am currently working on a little django app, the app is like a social media app.
User can post, like and comment.
I recently created the User Profiles. Which I can now see the user for that user profile in my view, but I cant seem to dig into the Posts that may be related to the UserProfile.
what I am trying to do is in my view of HTML, I want to be able to get the post from the userprofile and the comment, and likes.
But I have tried everything and nothing works.
Currently in my HTML view I have rendered {{ profile.user }} and it displays the users name, but If I try profile.user.post or profile.user.comments I get nothing.
Here is some code to show where I am at.
Any help would be much appreciated.
Profile View.
def profile(request):
profile = get_object_or_404(UserProfile, user=request.user)
template = 'profiles/profile.html'
context = {
'profile': profile,
# 'posts': posts
}
return render(request, template, context)
Profile Model
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 UserProfile(models.Model):
"""
A user profile model to link posts and likes
"""
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
def __str__(self):
return self.user.username
Post Model
from django.db import models
from django.contrib.auth.models import User
from cloudinary.models import CloudinaryField
from profiles.models import UserProfile
class Post(models.Model):
user_profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE, null=True, blank=True, related_name='user_posts')
title = models.CharField(max_length=220, unique=True)
location = models.CharField(max_length=220)
rating = models.DecimalField(
max_digits=6, decimal_places=2)
#slug = models.SlugField(max_length=220, unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="activity_post")
updated_on = models.DateTimeField(auto_now=True)
description = models.TextField()
featured_image = CloudinaryField('image', blank=False)
created_on = models.DateTimeField(auto_now_add=True)
likes = models.ManyToManyField(User, related_name='activity_likes', blank=True)
like_count = models.BigIntegerField(default='0')
class Meta:
ordering = ['-created_on']
def __str__(self):
return self.title
def number_of_likes(self):
return self.likes.count()
def liked_by_user(self):
return self.likes.values_list('id', flat=True)
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
name = models.CharField(max_length=80)
email = models.EmailField()
body = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['created_on']
def __str__(self):
return f"Comment {self.body} by {self.name}"
enter code here
My Signal to create / save the profile, also have it registered in apps.py
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import UserProfile
#receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.profile.save()

Django REST API endpoint for specific url

Im trying to create an endpoint for a post and its comments in the following format:
/posts (view all posts)
/posts/{id} (view post by id)
/posts/{id}/comments (view comments for a post)
The first 2 work, but for the last one I have /comments rather than the url i would like and I am not sure how to go about that, I think I need to change my models for it.
My current models (its using default Django User):
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class PostComment(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
comment = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.post.title
And urls:
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'posts', views.PostViewSet)
router.register(r'comments', views.PostCommentViewSet)
Edit: this are the viewsets
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all().order_by('id')
serializer_class = UserSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all().order_by('created_at')
serializer_class = PostSerializer
class PostCommentViewSet(viewsets.ModelViewSet):
queryset = PostComment.objects.all().order_by('created_at')
serializer_class = PostCommentSerializer
You can achieve this by writing the custom viewset actions--(drf doc),
from rest_framework.decorators import action
from rest_framework.response import Response
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all().order_by('created_at')
serializer_class = PostSerializer
#action(detail=True, url_path='comments', url_name='post-comments')
def comments(self, request, *args, **kwargs):
queryset = PostComment.objects.filter(post=kwargs['pk'])
serializer = PostCommentSerializer(queryset, many=True, context= {'request':request, 'view':self})
return Response(data=serializer.data)
Your view should be something similar to this -
class PostCommentViewSet(viewsets.ModelViewSet):
queryset = PostComment.objects.all().order_by('created_at')
serializer_class = PostCommentSerializer
#action(detail=True)
def comments(self, request, id=None):
....
You can refer to DRF documentation for more detail here - https://www.django-rest-framework.org/api-guide/routers/#routing-for-extra-actions
If you want to use the router, then this is probably achievable by implementing a custom router, like in this example: https://www.django-rest-framework.org/api-guide/routers/#example
I think you forgot to register the viewset route parameters with the action decorator
https://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing
it should work if you had in your viewset
from rest_framework.decorators import action
#actions(detail=True)
def comments(self, request, pk):
# things to do here

Model Placement

It is necessary to place posts on the site so that they can be created and edited, but in the site design the posts are separated by sliders and other elements, how is it possible to implement this in such a way that the other layout elements are, so to speak, ignored?
I tried to solve this problem by creating additional models, that is, the posts before the separating elements (sliders in this case) should be taken in one class, and the subsequent ones in another and further edited in such a way. Of course this is a crutch, but I don’t know how to configure it, because together these models are not displayed for me, that is, either one or the other. I also created a separate class for the Main Post, because it has a different design and, as a matter of fact, always hangs in the top, but of course it’s ideal to implement such that the model has such an attribute that you can indicate to a specific post that it is the main and most located at the beginning of the site.
Here is my code (let's say that we added Posts_one, Posts_two, if we talk about the case of using a crutch, which I also failed):
models.py:
from django.conf import settings
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Toppost(models.Model):
title = models.CharField(max_length=200)
caption = models.CharField(max_length=150, default='краткое описание поста')
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
class Post(models.Model):
title = models.CharField(max_length=200)
caption = models.CharField(max_length=150, default='краткое описание поста')
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
views.py:
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from .models import Post, Toppost
def posts_list(request):
posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
return render(request, 'blog/posts_list.html', {'posts': posts})
topposts = Toppost.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
return render(request, 'blog/posts_list.html', {'topposts': topposts})
I also attach a screenshot with a diagram of how it should look. Ideally, of course, it’s interesting to know how this can be implemented so that all this is one Post model and does not destroy the layout of the site and that you can select any post as Main_Post.
I'd simply add an attribute for a post to define whether it's a top post or not.
class Post(models.Model):
# ... all the other stuff ...
is_top_post = models.BooleanField(default=False)
# ... all the other stuff ...
# ...
def posts_list(request):
all_posts = Post.objects.filter(published_date__lte=timezone.now()).order_by("published_date")
non_top_posts = []
top_posts = []
for post in all_posts:
(top_posts if post.is_top_post else non_top_posts).append(post)
return render(request, "blog/posts_list.html", {"posts": non_top_posts, "top_posts": top_posts})
You can also ensure that there is exactly one top post by adding a save override like
class Post(models.Model):
# ... all the other stuff ...
is_top_post = models.BooleanField(default=False)
# ... all the other stuff ...
def save(self, **kwargs):
if self.is_top_post:
# Un-top every other post
Post.objects.exclude(id=self.id).update(is_top_post=False)
return super().save(**kwargs)

ValueError in a Q&A like app for linking answers to question

I am learning to build a Q&A app using Django. I have set up models for posts, comments and users. I am not able to successfully link the id for the posts to the comments.
I have tried linking the comments to the posts by overriding the ModelForm on Django unsuccessfully.
This is in my views.py file:
class CommentCreateView(LoginRequiredMixin, CreateView):
model = Comment
fields = ['comment']
def form_valid(self, form):
form.instance.author = self.request.user
form.instance.post_id = self.kwargs['post_id']
return super(CommentCreateView, self).form_valid(form)
My models.py has the model for comments are follows:
class Comment(models.Model):
cid = models.AutoField(primary_key=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
post_id = models.ForeignKey(Post, on_delete=models.CASCADE)
comment = models.TextField()
comment_date = models.DateTimeField(default=timezone.now)
def save(self, *args, **kwargs):
super(Comment, self).save(*args, **kwargs)
def __str__(self):
return self.comment
def get_absolute_url(self):
return reverse('blog-home')
and my url path is as follows:
path('comment/<int:post_id>', PostCommentListView.as_view(), name='user-comments')
I expect the comments to get linked with the posts via the ForeignKey. But when I tried doing the same, I get an error as described below:
ValueError at /post/11/comment/
Cannot assign "11": "Comment.post_id" must be a "Post" instance.
Exception Value:
Cannot assign "11": "Comment.post_id" must be a "Post" instance.
The error show that it expect an Post object. Try this
form.instance.post_id = Post.objects.get(pk=self.kwargs['post_id'])

Categories