how to play with ordering in class based view - python

I'm using class based view and for instance
class PostListByMostViewedView(ListView):
model = Post
template_name = 'community/mostviewed_home.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
ordering = ['-views']
paginate_by = 5
Models.py
class Post(models.Model):
views = models.PositiveIntegerField(default=0)
then posts are listed by view. but
I have different model
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
I want to order by the number of post.
I thought this would work
ordering = ['-post.comment_set.count']
but this fails
no such column: post.comment_set.count

You can .annotate(..) [Django-doc] the Post objects with the number of items in the comment_set, and then .order_by(..) [Django-doc] that annotation:
from django.db.models import Count
class PostListByMostViewedView(ListView):
model = Post
queryset = Post.objects.annotate(
ncomments=Count('comment')
).order_by('-ncomments')
template_name = 'community/mostviewed_home.html'
context_object_name = 'posts'
paginate_by = 5
The nice thing is that every Post object from the queryset now has an extra attribute ncomments that contains the number of comments.

Related

Correct Way to Structure Models, Views and Serializers

I have the following structure of Parent and Child models, where the child references the parent.
class ParentModel(models.Model):
name = models.CharField(max_length=255)
class ChildModel(models.Model):
name = models.CharField(max_length=255)
parent = models.ForeignKey(
ParentModel, related_name='children', on_delete=models.CASCADE
)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
class ParentSerializer(serializers.ModelSerializer):
class Meta:
model = ParentModel
fields = (
'name',
'children',
)
class ChildSerializer(serializers.ModelSerializer):
class Meta:
models = ChildModel
fields = (
'name'
)
class ParentViewSet(viewsets.ModelViewSet):
serializer_class = ParentSerializer
queryset = ParentModel.objects.all()
class ChildViewSet(viewsets.ModelViewSet):
serializer_class = ChildSerializer
def get_queryset(self):
user = self.request.user
return ChildModel.objects.filter(created_by=user)
I would like for ParentSerializer.children to only include the ChildModel objects with created_by as self.request.user.
What is the correct way to filter ParentSerializer.children to the current user?
I am open to changing the models as well.
First I think you got a n+1 issue with your code.
When DRF will serialize ParentModel, accessing current_parent.children.all() will produce an SQL query for each parent.
To prevent this you can use prefetch_related so:
class ParentViewSet(viewsets.ModelViewSet):
serializer_class = ParentSerializer
queryset = ParentModel.objects.prefetch_related(Prefetch("children"))
This will result in 2 SQL queries instead of N+1 (with N being the number of ParentModel row).
Additionnaly you can use prefetch_related to filter the related model:
class ParentViewSet(viewsets.ModelViewSet):
serializer_class = ParentSerializer
def get_queryset(self):
user = self.request.user
return ParentModel.objects.prefetch_related(Prefetch("children", queryset=ChildrenModel.objects.filter(created_by=user)))
Which is what you are looking for I think.

Django Custom Queryset Using Listview if Parameters Exist in URL

I have a listview for my blog:
#views.py
class BlogListView(ListView):
model = Blog
template_name = 'blog/index.html'
context_object_name = 'blogs'
ordering = ['-date_posted']
paginate_by = 5
#urls.py
path('', BlogListView.as_view(), name='blog-index'),
In my model I have different type of blogs, such as video blog or text blog. my model is like this:
class Blog(models.Model):
TYPE = (
('Video', 'Video'),
('Text', 'Text'),
)
type = models.CharField(max_length=10, choices=TYPE, default='Text')
Now I want to use request.GET.get('type') to query different types. For example if I go to the url, 127.0.0.1:8000/?type=video I want only blog that are the type video to show. Is it possible to do this with only this listview, or do I have to create others. I need help with making of this feature.
Yes, you can implement this in the ListView by overriding the .get_queryset(…) method [Django-doc]:
class BlogListView(ListView):
model = Blog
template_name = 'blog/index.html'
context_object_name = 'blogs'
ordering = ['-date_posted']
paginate_by = 5
def get_queryset(self):
type = self.request.GET.get('type')
qs = super().get_queryset()
if type is not None:
return qs.filter(type__iexact=type)
return qs

Reason for filter(admin_approved=True,valid_from__lte=now, valid_to__gte=now) not working immediately?

I have a project where there are posts that I want to be displayed for a certain period of time valid_from and valid_to
so the issue here is that I have added the correct filters and choosen the time but the posts keep displaying although the time valid_to has passed. I was in doubt until I added a countdown which became negative and exceeded the time valid_to value.
I am trying to understand the reason behind this issue and how to fix it?
here is the models:
class Post(models.Model):
valid_from = models.DateTimeField(blank=True, null=True)
valid_to = models.DateTimeField(blank=True, null=True)
Here is the veiws:
class PostListView(ListView):
model = Post
template_name = "post.html"
context_object_name = 'posts'
now = timezone.now()
queryset = Post.objects.filter(valid_from__lte=now, valid_to__gte=now)
now will not update, it will simply take the value when the class is interpreted for the first time and then reuse that value every time.
You can override the get_queryset(…) method [Django-doc], but perhaps more elegant is using the Now() expression [Django-doc] of the database:
from django.db.models.functions import Now
class PostListView(ListView):
model = Post
template_name = 'post.html'
context_object_name = 'posts'
queryset = Post.objects.filter(
valid_from__lte=Now(),
valid_to__gte=Now()
)
This will use the database time instead.
If you want to override the get_queryset(…) method instead, you write:
class PostListView(ListView):
model = Post
template_name = 'post.html'
context_object_name = 'posts'
def get_queryset(self, *args, **kwargs):
now = timezone.now()
return super().get_queryset(*args, **kwargs).filter(
valid_from__lte=now,
valid_to__gte=now
)

Django - ListView - list assigned images to the post

Hello I would like to list all images which were added to my post in my Blog application.
Models.py
class Post(models.Model):
title = models.CharField(max_length=100)
content = RichTextField(blank=True, null=True)
class PostImage(models.Model):
post = models.ForeignKey(Post, default=None, on_delete=models.CASCADE, related_name='postimages')
image = models.ImageField(upload_to='gallery/')
I guess I should override the get_queryset method but dont know how. Any ideas? Thanks.
EDIT:
Views.py
class PostGalleryView(ListView):
model = PostImage
template_name = 'blog/gallery.html'
context_object_name = 'images'
class PostListView(ListView):
model = Post
template_name = 'blog/home.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
public_posts = Post.objects.all()
return public_posts

How do I create a date range filter in a class based view?

My class based view in "views.py"
class PostListView(ListView):
model = Post.objects.filter(created__range=["2020-03-09", "2020-03-31"])
template_name = 'main/problems.html'
context_object_name = 'posts'
ordering = ['-created']
I have a variable "created" for when the post is created but dont know how to filter the post within a range.
The .model attribute [Django-doc] refers to the model class, but you can specify the .queryset attribute [Django-doc]:
class PostListView(ListView):
queryset = Post.objects.filter(created__range=['2020-03-09', '2020-03-31'])
template_name = 'main/problems.html'
context_object_name = 'posts'
ordering = ['-created']

Categories